add fee budget support to random strategy

This commit is contained in:
narumi 2024-05-26 22:38:17 +08:00
parent 0f03bc785b
commit 9cbf8a0ecf
3 changed files with 44 additions and 10 deletions

View File

@ -1,6 +1,7 @@
package common package common
import ( import (
"sync"
"time" "time"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
@ -11,6 +12,8 @@ import (
type FeeBudget struct { type FeeBudget struct {
DailyFeeBudgets map[string]fixedpoint.Value `json:"dailyFeeBudgets,omitempty"` DailyFeeBudgets map[string]fixedpoint.Value `json:"dailyFeeBudgets,omitempty"`
State *State `persistence:"state"` State *State `persistence:"state"`
mu sync.Mutex
} }
func (f *FeeBudget) Initialize() { func (f *FeeBudget) Initialize() {
@ -34,6 +37,11 @@ func (f *FeeBudget) IsBudgetAllowed() bool {
return true return true
} }
if f.State.IsOver24Hours() {
f.State.Reset()
return true
}
for asset, budget := range f.DailyFeeBudgets { for asset, budget := range f.DailyFeeBudgets {
if fee, ok := f.State.AccumulatedFees[asset]; ok { if fee, ok := f.State.AccumulatedFees[asset]; ok {
if fee.Compare(budget) >= 0 { if fee.Compare(budget) >= 0 {
@ -55,18 +63,18 @@ func (f *FeeBudget) HandleTradeUpdate(trade types.Trade) {
// safe check // safe check
if f.State.AccumulatedFees == nil { if f.State.AccumulatedFees == nil {
f.mu.Lock()
f.State.AccumulatedFees = make(map[string]fixedpoint.Value) f.State.AccumulatedFees = make(map[string]fixedpoint.Value)
f.mu.Unlock()
} }
f.State.AccumulatedFees[trade.FeeCurrency] = f.State.AccumulatedFees[trade.FeeCurrency].Add(trade.Fee) f.State.AccumulatedFees[trade.FeeCurrency] = f.State.AccumulatedFees[trade.FeeCurrency].Add(trade.Fee)
f.State.AccumulatedVolume = f.State.AccumulatedVolume.Add(trade.Quantity)
log.Infof("[FeeBudget] accumulated fee: %s %s", f.State.AccumulatedFees[trade.FeeCurrency].String(), trade.FeeCurrency) log.Infof("[FeeBudget] accumulated fee: %s %s", f.State.AccumulatedFees[trade.FeeCurrency].String(), trade.FeeCurrency)
} }
type State struct { type State struct {
AccumulatedFeeStartedAt time.Time `json:"accumulatedFeeStartedAt,omitempty"` AccumulatedFeeStartedAt time.Time `json:"accumulatedFeeStartedAt,omitempty"`
AccumulatedFees map[string]fixedpoint.Value `json:"accumulatedFees,omitempty"` AccumulatedFees map[string]fixedpoint.Value `json:"accumulatedFees,omitempty"`
AccumulatedVolume fixedpoint.Value `json:"accumulatedVolume,omitempty"`
} }
func (s *State) IsOver24Hours() bool { func (s *State) IsOver24Hours() bool {
@ -81,5 +89,4 @@ func (s *State) Reset() {
s.AccumulatedFeeStartedAt = dateTime s.AccumulatedFeeStartedAt = dateTime
s.AccumulatedFees = make(map[string]fixedpoint.Value) s.AccumulatedFees = make(map[string]fixedpoint.Value)
s.AccumulatedVolume = fixedpoint.Zero
} }

View File

@ -2,6 +2,7 @@ package common
import ( import (
"testing" "testing"
"time"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
@ -46,7 +47,10 @@ func TestFeeBudget(t *testing.T) {
for _, trade := range c.trades { for _, trade := range c.trades {
feeBudget.HandleTradeUpdate(trade) feeBudget.HandleTradeUpdate(trade)
} }
assert.Equal(t, c.expected, feeBudget.IsBudgetAllowed()) assert.Equal(t, c.expected, feeBudget.IsBudgetAllowed())
// test reset
feeBudget.State.AccumulatedFeeStartedAt = feeBudget.State.AccumulatedFeeStartedAt.Add(-24 * time.Hour)
assert.True(t, feeBudget.IsBudgetAllowed())
} }
} }

View File

@ -24,6 +24,7 @@ func init() {
type Strategy struct { type Strategy struct {
*common.Strategy *common.Strategy
*common.FeeBudget
Environment *bbgo.Environment Environment *bbgo.Environment
Market types.Market Market types.Market
@ -45,6 +46,10 @@ func (s *Strategy) Initialize() error {
if s.Strategy == nil { if s.Strategy == nil {
s.Strategy = &common.Strategy{} s.Strategy = &common.Strategy{}
} }
if s.FeeBudget == nil {
s.FeeBudget = &common.FeeBudget{}
}
return nil return nil
} }
@ -71,11 +76,25 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {}
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
s.Strategy.Initialize(ctx, s.Environment, session, s.Market, s.ID(), s.InstanceID()) s.Strategy.Initialize(ctx, s.Environment, session, s.Market, s.ID(), s.InstanceID())
s.FeeBudget.Initialize()
session.UserDataStream.OnStart(func() { session.UserDataStream.OnStart(func() {
if s.OnStart { if !s.OnStart {
s.placeOrder() return
} }
if !s.FeeBudget.IsBudgetAllowed() {
return
}
s.placeOrder(ctx)
})
session.UserDataStream.OnTradeUpdate(func(trade types.Trade) {
if trade.Symbol != s.Symbol {
return
}
s.FeeBudget.HandleTradeUpdate(trade)
}) })
// the shutdown handler, you can cancel all orders // the shutdown handler, you can cancel all orders
@ -86,15 +105,19 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
}) })
s.cron = cron.New() s.cron = cron.New()
s.cron.AddFunc(s.Schedule, s.placeOrder) s.cron.AddFunc(s.Schedule, func() {
if !s.FeeBudget.IsBudgetAllowed() {
return
}
s.placeOrder(ctx)
})
s.cron.Start() s.cron.Start()
return nil return nil
} }
func (s *Strategy) placeOrder() { func (s *Strategy) placeOrder(ctx context.Context) {
ctx := context.Background()
baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency) baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency)
if !ok { if !ok {
log.Errorf("base balance not found") log.Errorf("base balance not found")