strategy: DynamicSpreadSettings struct to make it more clean

This commit is contained in:
Andy Cheng 2022-05-27 16:24:50 +08:00
parent f452f8eb94
commit 98b794f265

View File

@ -49,6 +49,81 @@ type BollingerSetting struct {
BandWidth float64 `json:"bandWidth"` BandWidth float64 `json:"bandWidth"`
} }
type DynamicSpreadSettings struct {
Enabled bool `json:"enabled"`
// Window is the window of the SMAs of spreads
Window int `json:"window"`
// AskSpreadScale is used to define the ask spread range with the given percentage.
AskSpreadScale *bbgo.PercentageScale `json:"askSpreadScale"`
// BidSpreadScale is used to define the bid spread range with the given percentage.
BidSpreadScale *bbgo.PercentageScale `json:"bidSpreadScale"`
DynamicAskSpread *indicator.SMA
DynamicBidSpread *indicator.SMA
}
// Update dynamic spreads
func (ds *DynamicSpreadSettings) Update(kline types.KLine) {
if !ds.Enabled {
return
}
ampl := (kline.GetHigh().Float64() - kline.GetLow().Float64()) / kline.GetOpen().Float64()
switch kline.Direction() {
case types.DirectionUp:
ds.DynamicAskSpread.Update(ampl)
ds.DynamicBidSpread.Update(0)
case types.DirectionDown:
ds.DynamicBidSpread.Update(ampl)
ds.DynamicAskSpread.Update(0)
default:
ds.DynamicAskSpread.Update(0)
ds.DynamicBidSpread.Update(0)
}
}
// GetAskSpread returns current ask spread
func (ds *DynamicSpreadSettings) GetAskSpread() (askSpread float64, err error) {
if !ds.Enabled {
return 0, errors.New("dynamic spread is not enabled")
}
if ds.AskSpreadScale != nil && ds.DynamicAskSpread.Length() >= ds.Window {
askSpread, err = ds.AskSpreadScale.Scale(ds.DynamicAskSpread.Last())
if err != nil {
log.WithError(err).Errorf("can not calculate dynamicAskSpread")
return 0, err
}
return askSpread, nil
}
return 0, errors.New("incomplete dynamic spread settings or not enough data yet")
}
// GetBidSpread returns current dynamic bid spread
func (ds *DynamicSpreadSettings) GetBidSpread() (bidSpread float64, err error) {
if !ds.Enabled {
return 0, errors.New("dynamic spread is not enabled")
}
if ds.BidSpreadScale != nil && ds.DynamicBidSpread.Length() >= ds.Window {
bidSpread, err = ds.BidSpreadScale.Scale(ds.DynamicBidSpread.Last())
if err != nil {
log.WithError(err).Errorf("can not calculate dynamicBidSpread")
return 0, err
}
return bidSpread, nil
}
return 0, errors.New("incomplete dynamic spread settings or not enough data yet")
}
type Strategy struct { type Strategy struct {
*bbgo.Graceful *bbgo.Graceful
*bbgo.Notifiability *bbgo.Notifiability
@ -78,19 +153,8 @@ type Strategy struct {
// AskSpread overrides the spread setting, this spread will be used for the sell order // AskSpread overrides the spread setting, this spread will be used for the sell order
AskSpread fixedpoint.Value `json:"askSpread,omitempty"` AskSpread fixedpoint.Value `json:"askSpread,omitempty"`
// DynamicSpreadWindow enables the automatic adjustment to bid and ask spread. // DynamicSpread enables the automatic adjustment to bid and ask spread.
// When DynamicSpreadWindow is set and is larger than 0, the spreads are calculated based on the SMA of amplitude of DynamicSpread DynamicSpreadSettings `json:"dynamicSpread,omitempty"`
// [DynamicSpreadWindow] K-lines
DynamicSpreadWindow int `json:"dynamicSpreadWindow,omitempty"`
// DynamicAskSpreadScale is used to define the ask spread range with the given percentage.
DynamicAskSpreadScale *bbgo.PercentageScale `json:"dynamicAskSpreadScale"`
// DynamicBidSpreadScale is used to define the bid spread range with the given percentage.
DynamicBidSpreadScale *bbgo.PercentageScale `json:"dynamicBidSpreadScale"`
DynamicAskSpread *indicator.SMA
DynamicBidSpread *indicator.SMA
// MinProfitSpread is the minimal order price spread from the current average cost. // MinProfitSpread is the minimal order price spread from the current average cost.
// For long position, you will only place sell order above the price (= average cost * (1 + minProfitSpread)) // For long position, you will only place sell order above the price (= average cost * (1 + minProfitSpread))
@ -290,75 +354,6 @@ func (s *Strategy) getCurrentAllowedExposurePosition(bandPercentage float64) (fi
return s.MaxExposurePosition, nil return s.MaxExposurePosition, nil
} }
// IsDynamicSpreadEnabled checks dynamic spread functionality enabled or not
func (s *Strategy) IsDynamicSpreadEnabled() bool {
if s.DynamicSpreadWindow > 0 && s.DynamicAskSpreadScale != nil && s.DynamicBidSpreadScale != nil {
return true
}
return false
}
// Update dynamic spreads
func (s *Strategy) updateDynamicSpread(kline types.KLine) {
if !s.IsDynamicSpreadEnabled() {
return
}
ampl := (kline.GetHigh().Float64() - kline.GetLow().Float64()) / kline.GetOpen().Float64()
switch kline.Direction() {
case types.DirectionUp:
s.DynamicAskSpread.Update(ampl)
s.DynamicBidSpread.Update(0)
case types.DirectionDown:
s.DynamicBidSpread.Update(ampl)
s.DynamicAskSpread.Update(0)
default:
s.DynamicAskSpread.Update(0)
s.DynamicBidSpread.Update(0)
}
}
// Get current dynamic ask spread
func (s *Strategy) getDynamicAskSpread() (dynamicAskSpread float64, err error) {
if !s.IsDynamicSpreadEnabled() {
return 0, errors.New("dynamic spread is not enabled")
}
if s.DynamicAskSpreadScale != nil && s.DynamicAskSpread.Length() >= s.DynamicSpreadWindow {
dynamicAskSpread, err = s.DynamicAskSpreadScale.Scale(s.DynamicAskSpread.Last())
if err != nil {
log.WithError(err).Errorf("can not calculate dynamicAskSpread")
return 0, err
}
return dynamicAskSpread, nil
}
log.Infof("dynamicAskSpread: %v", dynamicAskSpread)
return 0, errors.New("incomplete dynamic spread settings or not enough data yet")
}
// Get current dynamic bid spread
func (s *Strategy) getDynamicBidSpread() (dynamicBidSpread float64, err error) {
if !s.IsDynamicSpreadEnabled() {
return 0, errors.New("dynamic spread is not enabled")
}
if s.DynamicBidSpreadScale != nil && s.DynamicBidSpread.Length() >= s.DynamicSpreadWindow {
dynamicBidSpread, err = s.DynamicBidSpreadScale.Scale(s.DynamicBidSpread.Last())
if err != nil {
log.WithError(err).Errorf("can not calculate dynamicBidSpread")
return 0, err
}
return dynamicBidSpread, nil
}
return 0, errors.New("incomplete dynamic spread settings or not enough data yet")
}
func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExecutor, midPrice fixedpoint.Value, kline *types.KLine) { func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExecutor, midPrice fixedpoint.Value, kline *types.KLine) {
bidSpread := s.Spread bidSpread := s.Spread
if s.BidSpread.Sign() > 0 { if s.BidSpread.Sign() > 0 {
@ -591,9 +586,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.Status = types.StrategyStatusRunning s.Status = types.StrategyStatusRunning
// Setup dynamic spread // Setup dynamic spread
if s.IsDynamicSpreadEnabled() { if s.DynamicSpread.Enabled {
s.DynamicBidSpread = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.DynamicSpreadWindow}} s.DynamicSpread.DynamicBidSpread = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.DynamicSpread.Window}}
s.DynamicAskSpread = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.DynamicSpreadWindow}} s.DynamicSpread.DynamicAskSpread = &indicator.SMA{IntervalWindow: types.IntervalWindow{s.Interval, s.DynamicSpread.Window}}
} }
s.OnSuspend(func() { s.OnSuspend(func() {
@ -747,14 +742,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
// Update spreads with dynamic spread // Update spreads with dynamic spread
if s.IsDynamicSpreadEnabled() { if s.DynamicSpread.Enabled {
s.updateDynamicSpread(kline) s.DynamicSpread.Update(kline)
dynamicBidSpread, err := s.getDynamicBidSpread() dynamicBidSpread, err := s.DynamicSpread.GetBidSpread()
if err == nil && dynamicBidSpread > 0 { if err == nil && dynamicBidSpread > 0 {
s.BidSpread = fixedpoint.NewFromFloat(dynamicBidSpread) s.BidSpread = fixedpoint.NewFromFloat(dynamicBidSpread)
log.Infof("new bid spread: %v", s.BidSpread.Percentage()) log.Infof("new bid spread: %v", s.BidSpread.Percentage())
} }
dynamicAskSpread, err := s.getDynamicAskSpread() dynamicAskSpread, err := s.DynamicSpread.GetAskSpread()
if err == nil && dynamicAskSpread > 0 { if err == nil && dynamicAskSpread > 0 {
s.AskSpread = fixedpoint.NewFromFloat(dynamicAskSpread) s.AskSpread = fixedpoint.NewFromFloat(dynamicAskSpread)
log.Infof("new ask spread: %v", s.AskSpread.Percentage()) log.Infof("new ask spread: %v", s.AskSpread.Percentage())