strategy/linregmaker: add dynamic quantity

This commit is contained in:
Andy Cheng 2022-11-18 16:42:51 +08:00
parent 9be9ea2a47
commit 8a81e68e27
3 changed files with 147 additions and 10 deletions

View File

@ -49,7 +49,7 @@ type DynamicExposureBollBand struct {
dynamicExposureBollBand *indicator.BOLL
}
// Initialize DynamicExposureBollBand
// initialize dynamic exposure with Bollinger Band
func (d *DynamicExposureBollBand) initialize(symbol string, session *bbgo.ExchangeSession) {
d.dynamicExposureBollBand = d.StandardIndicatorSet.BOLL(d.IntervalWindow, d.BandWidth)

View File

@ -0,0 +1,93 @@
package dynamicmetric
import (
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/indicator"
"github.com/c9s/bbgo/pkg/types"
"github.com/pkg/errors"
)
// DynamicQuantitySet uses multiple dynamic quantity rules to calculate the total quantity
type DynamicQuantitySet []DynamicQuantity
// Initialize dynamic quantity set
func (d *DynamicQuantitySet) Initialize(symbol string, session *bbgo.ExchangeSession) {
for i := range *d {
(*d)[i].Initialize(symbol, session)
}
}
// GetQuantity returns the quantity
func (d *DynamicQuantitySet) GetQuantity() (fixedpoint.Value, error) {
quantity := fixedpoint.Zero
for i := range *d {
v, err := (*d)[i].getQuantity()
if err != nil {
return fixedpoint.Zero, err
}
quantity = quantity.Add(v)
}
return quantity, nil
}
type DynamicQuantity struct {
// LinRegQty calculates quantity based on LinReg slope
LinRegDynamicQuantity *DynamicQuantityLinReg `json:"linRegDynamicQuantity"`
}
// Initialize dynamic quantity
func (d *DynamicQuantity) Initialize(symbol string, session *bbgo.ExchangeSession) {
switch {
case d.LinRegDynamicQuantity != nil:
d.LinRegDynamicQuantity.initialize(symbol, session)
}
}
func (d *DynamicQuantity) IsEnabled() bool {
return d.LinRegDynamicQuantity != nil
}
// getQuantity returns quantity
func (d *DynamicQuantity) getQuantity() (fixedpoint.Value, error) {
switch {
case d.LinRegDynamicQuantity != nil:
return d.LinRegDynamicQuantity.getQuantity()
default:
return fixedpoint.Zero, errors.New("dynamic quantity is not enabled")
}
}
// DynamicQuantityLinReg uses LinReg slope to calculate quantity
type DynamicQuantityLinReg struct {
// DynamicQuantityLinRegScale is used to define the quantity range with the given parameters.
DynamicQuantityLinRegScale *bbgo.PercentageScale `json:"dynamicQuantityLinRegScale"`
// QuantityLinReg to define the interval and window of the LinReg
QuantityLinReg *indicator.LinReg `json:"quantityLinReg"`
}
// initialize LinReg dynamic quantity
func (d *DynamicQuantityLinReg) initialize(symbol string, session *bbgo.ExchangeSession) {
// Subscribe for LinReg
session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{
Interval: d.QuantityLinReg.Interval,
})
// Initialize LinReg
kLineStore, _ := session.MarketDataStore(symbol)
d.QuantityLinReg.BindK(session.MarketDataStream, symbol, d.QuantityLinReg.Interval)
if klines, ok := kLineStore.KLinesOfInterval(d.QuantityLinReg.Interval); ok {
d.QuantityLinReg.LoadK((*klines)[0:])
}
}
// getQuantity returns quantity
func (d *DynamicQuantityLinReg) getQuantity() (fixedpoint.Value, error) {
v, err := d.DynamicQuantityLinRegScale.Scale(d.QuantityLinReg.Last())
if err != nil {
return fixedpoint.Zero, err
}
return fixedpoint.NewFromFloat(v), nil
}

View File

@ -48,8 +48,6 @@ type Strategy struct {
types.IntervalWindow
bbgo.QuantityOrAmount
// ReverseEMA is used to determine the long-term trend.
// Above the ReverseEMA is the long trend and vise versa.
// All the opposite trend position will be closed upon the trend change
@ -109,15 +107,21 @@ type Strategy struct {
DynamicSpread dynamicmetric.DynamicSpread `json:"dynamicSpread,omitempty"`
// MaxExposurePosition is the maximum position you can hold
// +10 means you can hold 10 ETH long position by maximum
// -10 means you can hold -10 ETH short position by maximum
// 10 means you can hold 10 ETH long/short position by maximum
MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"`
// DynamicExposure is used to define the exposure position range with the given percentage.
// When DynamicExposure is set, your MaxExposurePosition will be calculated dynamically according to the bollinger
// band you set.
// When DynamicExposure is set, your MaxExposurePosition will be calculated dynamically
DynamicExposure dynamicmetric.DynamicExposure `json:"dynamicExposure"`
bbgo.QuantityOrAmount
// DynamicQuantityIncrease calculates the increase position order quantity dynamically
DynamicQuantityIncrease dynamicmetric.DynamicQuantitySet `json:"dynamicQuantityIncrease"`
// DynamicQuantityDecrease calculates the decrease position order quantity dynamically
DynamicQuantityDecrease dynamicmetric.DynamicQuantitySet `json:"dynamicQuantityDecrease"`
session *bbgo.ExchangeSession
// ExitMethods are various TP/SL methods
@ -197,6 +201,14 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
if s.DynamicExposure.IsEnabled() {
s.DynamicExposure.Initialize(s.Symbol, session)
}
// Setup dynamic quantities
if len(s.DynamicQuantityIncrease) > 0 {
s.DynamicQuantityIncrease.Initialize(s.Symbol, session)
}
if len(s.DynamicQuantityDecrease) > 0 {
s.DynamicQuantityDecrease.Initialize(s.Symbol, session)
}
}
func (s *Strategy) Validate() error {
@ -266,10 +278,42 @@ func (s *Strategy) getOrderPrices(midPrice fixedpoint.Value) (askPrice fixedpoin
// getOrderQuantities returns sell and buy qty
func (s *Strategy) getOrderQuantities(askPrice fixedpoint.Value, bidPrice fixedpoint.Value) (sellQuantity fixedpoint.Value, buyQuantity fixedpoint.Value) {
// TODO: dynamic qty to determine qty
// TODO: spot, margin, and futures
sellQuantity = s.QuantityOrAmount.CalculateQuantity(askPrice)
buyQuantity = s.QuantityOrAmount.CalculateQuantity(bidPrice)
// Dynamic qty
switch {
case s.mainTrendCurrent == types.DirectionUp:
var err error
if len(s.DynamicQuantityIncrease) > 0 {
buyQuantity, err = s.DynamicQuantityIncrease.GetQuantity()
if err != nil {
buyQuantity = s.QuantityOrAmount.CalculateQuantity(bidPrice)
}
}
if len(s.DynamicQuantityDecrease) > 0 {
sellQuantity, err = s.DynamicQuantityDecrease.GetQuantity()
if err != nil {
sellQuantity = s.QuantityOrAmount.CalculateQuantity(askPrice)
}
}
case s.mainTrendCurrent == types.DirectionDown:
var err error
if len(s.DynamicQuantityIncrease) > 0 {
sellQuantity, err = s.DynamicQuantityIncrease.GetQuantity()
if err != nil {
sellQuantity = s.QuantityOrAmount.CalculateQuantity(bidPrice)
}
}
if len(s.DynamicQuantityDecrease) > 0 {
buyQuantity, err = s.DynamicQuantityDecrease.GetQuantity()
if err != nil {
buyQuantity = s.QuantityOrAmount.CalculateQuantity(askPrice)
}
}
default:
sellQuantity = s.QuantityOrAmount.CalculateQuantity(askPrice)
buyQuantity = s.QuantityOrAmount.CalculateQuantity(bidPrice)
}
// Faster position decrease
if s.mainTrendCurrent == types.DirectionUp && s.FastLinReg.Last() < 0 && s.SlowLinReg.Last() < 0 {