mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
add xnav strategy
This commit is contained in:
parent
75415638e6
commit
ed1d0ea27e
|
@ -16,6 +16,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/swing"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/techsignal"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xbalance"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xnav"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xmaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/xpuremaker"
|
||||
)
|
||||
|
|
2
pkg/strategy/xnav/csv.go
Normal file
2
pkg/strategy/xnav/csv.go
Normal file
|
@ -0,0 +1,2 @@
|
|||
package xnav
|
||||
|
191
pkg/strategy/xnav/strategy.go
Normal file
191
pkg/strategy/xnav/strategy.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
package xnav
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
const ID = "xnav"
|
||||
|
||||
const stateKey = "state-v1"
|
||||
|
||||
func init() {
|
||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Since int64 `json:"since"`
|
||||
}
|
||||
|
||||
func (s *State) IsOver24Hours() bool {
|
||||
return util.Over24Hours(time.Unix(s.Since, 0))
|
||||
}
|
||||
|
||||
func (s *State) PlainText() string {
|
||||
return util.Render(`{{ .Asset }} transfer stats:
|
||||
daily number of transfers: {{ .DailyNumberOfTransfers }}
|
||||
daily amount of transfers {{ .DailyAmountOfTransfers.Float64 }}`, s)
|
||||
}
|
||||
|
||||
func (s *State) SlackAttachment() slack.Attachment {
|
||||
return slack.Attachment{
|
||||
// Pretext: "",
|
||||
// Text: text,
|
||||
Fields: []slack.AttachmentField{},
|
||||
Footer: util.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) Reset() {
|
||||
var beginningOfTheDay = util.BeginningOfTheDay(time.Now().Local())
|
||||
*s = State{
|
||||
Since: beginningOfTheDay.Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Notifiability *bbgo.Notifiability
|
||||
*bbgo.Graceful
|
||||
*bbgo.Persistence
|
||||
|
||||
Interval types.Duration `json:"interval"`
|
||||
ReportOnStart bool `json:"reportOnStart"`
|
||||
IgnoreDusts bool `json:"ignoreDusts"`
|
||||
state *State
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
return ID
|
||||
}
|
||||
|
||||
func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {}
|
||||
|
||||
func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string]*bbgo.ExchangeSession) {
|
||||
totalAssets := types.AssetMap{}
|
||||
totalBalances := types.BalanceMap{}
|
||||
lastPrices := map[string]float64{}
|
||||
for _, session := range sessions {
|
||||
balances := session.Account.Balances()
|
||||
if err := session.UpdatePrices(ctx); err != nil {
|
||||
log.WithError(err).Error("price update failed")
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range balances {
|
||||
if tb, ok := totalBalances[b.Currency]; ok {
|
||||
tb.Available += b.Available
|
||||
tb.Locked += b.Locked
|
||||
totalBalances[b.Currency] = tb
|
||||
} else {
|
||||
totalBalances[b.Currency] = b
|
||||
}
|
||||
}
|
||||
|
||||
prices := session.LastPrices()
|
||||
for m, p := range prices {
|
||||
lastPrices[m] = p
|
||||
}
|
||||
}
|
||||
|
||||
assets := totalBalances.Assets(lastPrices)
|
||||
for currency, asset := range assets {
|
||||
if s.IgnoreDusts && asset.InUSD < fixedpoint.NewFromFloat(10.0) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalAssets[currency] = asset
|
||||
}
|
||||
|
||||
s.Notifiability.Notify(totalAssets)
|
||||
|
||||
if s.state != nil {
|
||||
if s.state.IsOver24Hours() {
|
||||
s.state.Reset()
|
||||
}
|
||||
|
||||
s.SaveState()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) SaveState() {
|
||||
if err := s.Persistence.Save(s.state, ID, stateKey); err != nil {
|
||||
log.WithError(err).Errorf("%s can not save state: %+v", ID, s.state)
|
||||
} else {
|
||||
log.Infof("%s state is saved: %+v", ID, s.state)
|
||||
// s.Notifiability.Notify("%s %s state is saved", ID, s.Asset, s.state)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) newDefaultState() *State {
|
||||
return &State{}
|
||||
}
|
||||
|
||||
func (s *Strategy) LoadState() error {
|
||||
var state State
|
||||
if err := s.Persistence.Load(&state, ID, stateKey); err != nil {
|
||||
if err != service.ErrPersistenceNotExists {
|
||||
return err
|
||||
}
|
||||
|
||||
s.state = s.newDefaultState()
|
||||
s.state.Reset()
|
||||
} else {
|
||||
// we loaded it successfully
|
||||
s.state = &state
|
||||
|
||||
// update Asset name for legacy caches
|
||||
// s.state.Asset = s.Asset
|
||||
|
||||
log.Infof("%s state is restored: %+v", ID, s.state)
|
||||
s.Notifiability.Notify("%s state is restored", ID, s.state)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error {
|
||||
if s.Interval == 0 {
|
||||
return errors.New("interval can not be zero")
|
||||
}
|
||||
|
||||
if err := s.LoadState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
s.SaveState()
|
||||
})
|
||||
|
||||
if s.ReportOnStart {
|
||||
s.recordNetAssetValue(ctx, sessions)
|
||||
}
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(util.MillisecondsJitter(s.Interval.Duration(), 1000))
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
s.recordNetAssetValue(ctx, sessions)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -2,8 +2,12 @@ package types
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -40,10 +44,70 @@ type Asset struct {
|
|||
Total fixedpoint.Value `json:"total"`
|
||||
InUSD fixedpoint.Value `json:"inUSD"`
|
||||
InBTC fixedpoint.Value `json:"inBTC"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
type AssetMap map[string]Asset
|
||||
|
||||
func (m AssetMap) PlainText() (o string) {
|
||||
for _, a := range m {
|
||||
o += fmt.Sprintf("%s: %f (≈ %s) (≈ %s)",
|
||||
a.Currency,
|
||||
a.Total.Float64(),
|
||||
USD.FormatMoneyFloat64(a.InUSD.Float64()),
|
||||
BTC.FormatMoneyFloat64(a.InBTC.Float64()),
|
||||
) + "\n"
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
func (m AssetMap) Slice() (assets []Asset) {
|
||||
for _, a := range m {
|
||||
assets = append(assets, a)
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
func (m AssetMap) SlackAttachment() slack.Attachment {
|
||||
var fields []slack.AttachmentField
|
||||
var totalBTC, totalUSD fixedpoint.Value
|
||||
|
||||
var assets = m.Slice()
|
||||
|
||||
// sort assets
|
||||
sort.Slice(assets, func(i, j int) bool {
|
||||
return assets[i].InUSD > assets[j].InUSD
|
||||
})
|
||||
|
||||
for _, a := range assets {
|
||||
totalUSD += a.InUSD
|
||||
totalBTC += a.InBTC
|
||||
}
|
||||
|
||||
for _, a := range assets {
|
||||
fields = append(fields, slack.AttachmentField{
|
||||
Title: a.Currency,
|
||||
Value: fmt.Sprintf("%f (≈ %s) (≈ %s) (%.2f%%)",
|
||||
a.Total.Float64(),
|
||||
USD.FormatMoneyFloat64(a.InUSD.Float64()),
|
||||
BTC.FormatMoneyFloat64(a.InBTC.Float64()),
|
||||
math.Round(a.InUSD.Div(totalUSD).Float64() * 100.0),
|
||||
),
|
||||
Short: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return slack.Attachment{
|
||||
Title: fmt.Sprintf("Net Asset Value %s (≈ %s)",
|
||||
USD.FormatMoneyFloat64(totalUSD.Float64()),
|
||||
BTC.FormatMoneyFloat64(totalBTC.Float64()),
|
||||
),
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
type BalanceMap map[string]Balance
|
||||
|
||||
func (m BalanceMap) String() string {
|
||||
|
@ -66,6 +130,7 @@ func (m BalanceMap) Copy() (d BalanceMap) {
|
|||
func (m BalanceMap) Assets(prices map[string]float64) AssetMap {
|
||||
assets := make(AssetMap)
|
||||
|
||||
now := time.Now()
|
||||
for currency, b := range m {
|
||||
if b.Locked == 0 && b.Available == 0 {
|
||||
continue
|
||||
|
@ -74,6 +139,7 @@ func (m BalanceMap) Assets(prices map[string]float64) AssetMap {
|
|||
asset := Asset{
|
||||
Currency: currency,
|
||||
Total: b.Available + b.Locked,
|
||||
Time: now,
|
||||
}
|
||||
|
||||
btcusdt, hasBtcPrice := prices["BTCUSDT"]
|
||||
|
|
|
@ -3,7 +3,7 @@ package types
|
|||
import "github.com/leekchan/accounting"
|
||||
|
||||
var USD = accounting.Accounting{Symbol: "$ ", Precision: 2}
|
||||
var BTC = accounting.Accounting{Symbol: "BTC ", Precision: 2}
|
||||
var BTC = accounting.Accounting{Symbol: "BTC ", Precision: 8}
|
||||
var BNB = accounting.Accounting{Symbol: "BNB ", Precision: 4}
|
||||
|
||||
var FiatCurrencies = []string{"USDC", "USDT", "USD", "TWD", "EUR", "GBP", "BUSD"}
|
||||
|
|
|
@ -15,3 +15,6 @@ func BeginningOfTheDay(t time.Time) time.Time {
|
|||
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func Over24Hours(since time.Time) bool {
|
||||
return time.Since(since) >= 24 * time.Hour
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user