bbgo_origin/pkg/strategy/xnav/strategy.go

182 lines
4.1 KiB
Go
Raw Permalink Normal View History

2021-10-29 02:40:14 +00:00
package xnav
import (
"context"
"sync"
"time"
"github.com/c9s/bbgo/pkg/fixedpoint"
2022-08-22 18:12:26 +00:00
"github.com/c9s/bbgo/pkg/util/templateutil"
"github.com/pkg/errors"
2022-05-04 06:23:35 +00:00
"github.com/sirupsen/logrus"
"github.com/slack-go/slack"
2021-10-29 02:40:14 +00:00
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
)
const ID = "xnav"
const stateKey = "state-v1"
2022-05-04 06:23:35 +00:00
var log = logrus.WithField("strategy", ID)
2021-10-29 02:40:14 +00:00
func init() {
bbgo.RegisterStrategy(ID, &Strategy{})
}
type State struct {
Since int64 `json:"since"`
}
func (s *State) IsOver24Hours() bool {
2022-08-22 18:12:26 +00:00
return types.Over24Hours(time.Unix(s.Since, 0))
2021-10-29 02:40:14 +00:00
}
func (s *State) PlainText() string {
2022-08-22 18:12:26 +00:00
return templateutil.Render(`{{ .Asset }} transfer stats:
2021-10-29 02:40:14 +00:00
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{},
2022-08-22 18:12:26 +00:00
Footer: templateutil.Render("Since {{ . }}", time.Unix(s.Since, 0).Format(time.RFC822)),
2021-10-29 02:40:14 +00:00
}
}
func (s *State) Reset() {
2022-08-22 18:12:26 +00:00
var beginningOfTheDay = types.BeginningOfTheDay(time.Now().Local())
2021-10-29 02:40:14 +00:00
*s = State{
Since: beginningOfTheDay.Unix(),
}
}
type Strategy struct {
2022-05-04 06:23:35 +00:00
*bbgo.Environment
2021-10-29 02:40:14 +00:00
2022-05-04 06:55:18 +00:00
Interval types.Interval `json:"interval"`
2021-10-29 02:40:14 +00:00
ReportOnStart bool `json:"reportOnStart"`
IgnoreDusts bool `json:"ignoreDusts"`
2022-10-03 10:37:53 +00:00
State *State `persistence:"state"`
2021-10-29 02:40:14 +00:00
}
func (s *Strategy) ID() string {
return ID
}
var Ten = fixedpoint.NewFromInt(10)
2021-10-29 02:40:14 +00:00
func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {}
func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string]*bbgo.ExchangeSession) {
totalBalances := types.BalanceMap{}
2022-05-04 06:23:35 +00:00
allPrices := map[string]fixedpoint.Value{}
sessionBalances := map[string]types.BalanceMap{}
priceTime := time.Now()
// iterate the sessions and record them
for sessionName, session := range sessions {
// update the account balances and the margin information
2022-06-01 17:27:04 +00:00
if _, err := session.UpdateAccount(ctx); err != nil {
2022-04-26 08:13:07 +00:00
log.WithError(err).Errorf("can not update account")
return
}
account := session.GetAccount()
balances := account.Balances()
2022-05-04 06:23:35 +00:00
if err := session.UpdatePrices(ctx, balances.Currencies(), "USDT"); err != nil {
2021-10-29 02:40:14 +00:00
log.WithError(err).Error("price update failed")
return
}
2022-05-04 06:23:35 +00:00
sessionBalances[sessionName] = balances
totalBalances = totalBalances.Add(balances)
2021-10-29 02:40:14 +00:00
prices := session.LastPrices()
2022-05-04 06:23:35 +00:00
assets := balances.Assets(prices, priceTime)
// merge prices
2021-10-29 02:40:14 +00:00
for m, p := range prices {
2022-05-04 06:23:35 +00:00
allPrices[m] = p
2021-10-29 02:40:14 +00:00
}
2022-05-04 06:23:35 +00:00
s.Environment.RecordAsset(priceTime, session, assets)
2021-10-29 02:40:14 +00:00
}
displayAssets := types.AssetMap{}
totalAssets := totalBalances.Assets(allPrices, priceTime)
s.Environment.RecordAsset(priceTime, &bbgo.ExchangeSession{Name: "ALL"}, totalAssets)
for currency, asset := range totalAssets {
// calculated if it's dust only when InUSD (usd value) is defined.
2022-09-13 18:20:54 +00:00
if s.IgnoreDusts && !asset.InUSD.IsZero() && asset.InUSD.Compare(Ten) < 0 && asset.InUSD.Compare(Ten.Neg()) > 0 {
2021-10-29 02:40:14 +00:00
continue
}
displayAssets[currency] = asset
2021-10-29 02:40:14 +00:00
}
bbgo.Notify(displayAssets)
2021-10-29 02:40:14 +00:00
2022-10-03 10:37:53 +00:00
if s.State != nil {
if s.State.IsOver24Hours() {
s.State.Reset()
2021-10-29 02:40:14 +00:00
}
2022-10-03 10:45:24 +00:00
bbgo.Sync(ctx, s)
2021-10-29 02:40:14 +00:00
}
}
func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error {
2022-05-04 06:55:18 +00:00
if s.Interval == "" {
return errors.New("interval can not be empty")
2021-10-29 02:40:14 +00:00
}
2022-10-03 10:37:53 +00:00
if s.State == nil {
s.State = &State{}
s.State.Reset()
2021-10-29 02:40:14 +00:00
}
2022-10-03 08:01:08 +00:00
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
2021-10-29 02:40:14 +00:00
defer wg.Done()
2022-10-03 10:45:24 +00:00
bbgo.Sync(ctx, s)
2021-10-29 02:40:14 +00:00
})
if s.ReportOnStart {
s.recordNetAssetValue(ctx, sessions)
}
2022-05-04 06:23:35 +00:00
if s.Environment.BacktestService != nil {
log.Warnf("xnav does not support backtesting")
}
2022-05-04 06:55:18 +00:00
// TODO: if interval is supported, we can use kline as the ticker
if _, ok := types.SupportedIntervals[s.Interval]; ok {
2022-05-04 06:55:18 +00:00
}
2021-10-29 02:40:14 +00:00
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
}