strategy: use scale for dynamic spread

This commit is contained in:
Andy Cheng 2022-05-18 14:31:59 +08:00
parent 7d3181f3fd
commit b41cef4bd7
2 changed files with 82 additions and 39 deletions

View File

@ -58,15 +58,29 @@ exchangeStrategies:
# EXPERIMENTAL
# Dynamic spread is an experimental feature. Use at your own risk!
# DynamicSpreadWindow enables the automatic adjustment to bid and ask spread.
#
# dynamicSpreadWindow enables the automatic adjustment to bid and ask spread.
# When DynamicSpreadWindow is set and larger than 0, the spreads are calculated based on the SMA of amplitude of
# previous [DynamicSpreadWindow] K-lines. Max and min spreads limit the maximum and minimum of spreads. Set them
# to 0 to disable the limitation.
# previous [DynamicSpreadWindow] K-lines.
# dynamicSpreadWindow: 1
# minAskSpread: 0.05%
# minBidSpread: 0.05%
# maxAskSpread: 1
# maxBidSpread: 1
# dynamicAskSpreadScale:
# byPercentage:
# # exp means we want to use exponential scale, you can replace "exp" with "linear" for linear scale
# exp:
# # amplitude from low to high
# domain: [ 0.001, 0.01 ]
# # when amplitude is low, minimum ask is spread 0.05%
# # when amplitude is high, maximum ask is spread 0.2%
# range: [ 0.0005, 0.002 ]
# dynamicBidSpreadScale:
# byPercentage:
# # exp means we want to use exponential scale, you can replace "exp" with "linear" for linear scale
# exp:
# # amplitude from low to high
# domain: [ 0.001, 0.01 ]
# # when amplitude is low, minimum bid is spread 0.05%
# # when amplitude is high, maximum bid is spread 0.2%
# range: [ 0.0005, 0.002 ]
# maxExposurePosition is the maximum position you can hold
# +10 means you can hold 10 ETH long position by maximum

View File

@ -81,11 +81,13 @@ type Strategy struct {
// DynamicSpreadWindow 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
// [DynamicSpreadWindow] K-lines
DynamicSpreadWindow int `json:"dynamicSpreadWindow,omitempty"`
MinAskSpread fixedpoint.Value `json:"minAskSpread"`
MinBidSpread fixedpoint.Value `json:"minBidSpread"`
MaxAskSpread fixedpoint.Value `json:"maxAskSpread"`
MaxBidSpread fixedpoint.Value `json:"maxBidSpread"`
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
@ -288,6 +290,53 @@ func (s *Strategy) getCurrentAllowedExposurePosition(bandPercentage float64) (fi
return s.MaxExposurePosition, nil
}
// Update dynamic spreads
func (s *Strategy) updateDynamicSpread(kline types.KLine) {
if s.DynamicSpreadWindow > 0 {
high := kline.GetHigh().Float64()
open := kline.GetOpen().Float64()
low := kline.GetLow().Float64()
if kline.Direction() == types.DirectionUp {
s.DynamicAskSpread.Update((high - open) / open)
}
if kline.Direction() == types.DirectionDown {
s.DynamicBidSpread.Update((open - low) / open)
}
}
}
// Get current dynamic ask spread
func (s *Strategy) getDynamicAskSpread() (dynamicAskSpread float64, err error) {
if s.DynamicAskSpreadScale != nil && s.DynamicSpreadWindow > 0 && 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.DynamicBidSpreadScale != nil && s.DynamicSpreadWindow > 0 && 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) {
bidSpread := s.Spread
if s.BidSpread.Sign() > 0 {
@ -676,35 +725,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
// Update spreads
high := kline.GetHigh().Float64()
open := kline.GetOpen().Float64()
low := kline.GetLow().Float64()
if s.DynamicSpreadWindow > 0 && kline.Direction() == types.DirectionUp {
s.DynamicAskSpread.Update((high - open) / open)
}
if s.DynamicSpreadWindow > 0 && kline.Direction() == types.DirectionDown {
s.DynamicBidSpread.Update((open - low) / open)
}
if s.DynamicSpreadWindow > 0 && s.DynamicBidSpread.Length() >= s.DynamicSpreadWindow {
dynamicBidSpread := fixedpoint.NewFromFloat(s.DynamicBidSpread.Last())
if !s.MaxBidSpread.IsZero() && dynamicBidSpread.Compare(s.MaxBidSpread) > 0 {
s.BidSpread = s.MaxBidSpread
} else if !s.MinBidSpread.IsZero() && dynamicBidSpread.Compare(s.MinBidSpread) < 0 {
s.BidSpread = s.MinBidSpread
} else {
s.BidSpread = dynamicBidSpread
}
s.updateDynamicSpread(kline)
dynamicBidSpread, err := s.getDynamicBidSpread()
if err == nil && dynamicBidSpread > 0 {
s.BidSpread = fixedpoint.NewFromFloat(dynamicBidSpread)
log.Infof("new bid spread: %v", s.BidSpread.Percentage())
}
if s.DynamicSpreadWindow > 0 && s.DynamicAskSpread.Length() >= s.DynamicSpreadWindow {
dynamicAskSpread := fixedpoint.NewFromFloat(s.DynamicAskSpread.Last())
if !s.MaxAskSpread.IsZero() && dynamicAskSpread.Compare(s.MaxAskSpread) > 0 {
s.AskSpread = s.MaxAskSpread
} else if !s.MinAskSpread.IsZero() && dynamicAskSpread.Compare(s.MinAskSpread) < 0 {
s.AskSpread = s.MinAskSpread
} else {
s.AskSpread = dynamicAskSpread
}
dynamicAskSpread, err := s.getDynamicAskSpread()
if err == nil && dynamicAskSpread > 0 {
s.AskSpread = fixedpoint.NewFromFloat(dynamicAskSpread)
log.Infof("new ask spread: %v", s.AskSpread.Percentage())
}