mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
support strategy improvements:
- add taker buy base volume ratio option - add max base asset balance config - add min quote asset balance config - record orders and trades
This commit is contained in:
parent
6a999b2906
commit
40c3a5870f
|
@ -33,10 +33,19 @@ type Strategy struct {
|
|||
MovingAverageWindow int `json:"movingAverageWindow"`
|
||||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
MinVolume fixedpoint.Value `json:"minVolume"`
|
||||
TakerBuyRatio fixedpoint.Value `json:"takerBuyRatio"`
|
||||
MarginOrderSideEffect types.MarginOrderSideEffectType `json:"marginOrderSideEffect"`
|
||||
Targets []Target `json:"targets"`
|
||||
|
||||
// Max BaseAsset balance to buy
|
||||
MaxBaseAssetBalance fixedpoint.Value `json:"maxBaseAssetBalance"`
|
||||
MinQuoteAssetBalance fixedpoint.Value `json:"minQuoteAssetBalance"`
|
||||
|
||||
ScaleQuantity *bbgo.PriceVolumeScale `json:"scaleQuantity"`
|
||||
|
||||
orderStore *bbgo.OrderStore
|
||||
tradeStore *bbgo.TradeStore
|
||||
tradeC chan types.Trade
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
|
@ -59,7 +68,28 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: string(s.Interval)})
|
||||
}
|
||||
|
||||
func (s *Strategy) handleTradeUpdate(trade types.Trade) {
|
||||
s.tradeC <- trade
|
||||
}
|
||||
|
||||
func (s *Strategy) tradeCollector(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case trade := <-s.tradeC:
|
||||
s.tradeStore.Add(trade)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
// buffer 100 trades in the channel
|
||||
s.tradeC = make(chan types.Trade, 100)
|
||||
s.tradeStore = bbgo.NewTradeStore(s.Symbol)
|
||||
|
||||
// set default values
|
||||
if s.Interval == "" {
|
||||
s.Interval = types.Interval5m
|
||||
|
@ -69,7 +99,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.MovingAverageWindow = 99
|
||||
}
|
||||
|
||||
// buy when price drops -8%
|
||||
market, ok := session.Market(s.Symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("market %s is not defined", s.Symbol)
|
||||
|
@ -80,74 +109,123 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return fmt.Errorf("standardIndicatorSet is nil, symbol %s", s.Symbol)
|
||||
}
|
||||
|
||||
s.orderStore = bbgo.NewOrderStore(s.Symbol)
|
||||
s.orderStore.BindStream(session.UserDataStream)
|
||||
|
||||
var iw = types.IntervalWindow{Interval: s.Interval, Window: s.MovingAverageWindow}
|
||||
var ema = standardIndicatorSet.EWMA(iw)
|
||||
|
||||
go s.tradeCollector(ctx)
|
||||
|
||||
session.UserDataStream.OnTradeUpdate(s.handleTradeUpdate)
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
// skip k-lines from other symbols
|
||||
if kline.Symbol != s.Symbol {
|
||||
return
|
||||
}
|
||||
|
||||
closePrice := kline.GetClose()
|
||||
if closePrice > ema.Last() {
|
||||
closePriceF := kline.GetClose()
|
||||
if closePriceF > ema.Last() {
|
||||
return
|
||||
}
|
||||
|
||||
closePrice := fixedpoint.NewFromFloat(closePriceF)
|
||||
|
||||
if kline.Volume < s.MinVolume.Float64() {
|
||||
return
|
||||
}
|
||||
|
||||
s.Notify("Found %s support: the close price %f is under EMA %f and volume %f > minimum volume %f", s.Symbol, closePrice, ema.Last(), kline.Volume, s.MinVolume.Float64())
|
||||
if s.TakerBuyRatio > 0 {
|
||||
takerBuyBaseVolumeThreshold := kline.Volume * s.TakerBuyRatio.Float64()
|
||||
if kline.TakerBuyBaseAssetVolume < takerBuyBaseVolumeThreshold {
|
||||
s.Notify("%s: taker buy base volume %f is less than %f (volume ratio %f)",
|
||||
s.Symbol,
|
||||
kline.TakerBuyBaseAssetVolume,
|
||||
takerBuyBaseVolumeThreshold,
|
||||
s.TakerBuyRatio.Float64(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var quantity float64
|
||||
s.Notify("Found %s support: the close price %f is under EMA %f and volume %f > minimum volume %f",
|
||||
s.Symbol,
|
||||
closePrice.Float64(),
|
||||
ema.Last(),
|
||||
kline.Volume,
|
||||
s.MinVolume.Float64(), kline)
|
||||
|
||||
var quantity fixedpoint.Value
|
||||
if s.Quantity > 0 {
|
||||
quantity = s.Quantity.Float64()
|
||||
quantity = s.Quantity
|
||||
} else if s.ScaleQuantity != nil {
|
||||
var err error
|
||||
quantity, err = s.ScaleQuantity.Scale(closePrice, kline.Volume)
|
||||
qf, err := s.ScaleQuantity.Scale(closePrice.Float64(), kline.Volume)
|
||||
if err != nil {
|
||||
log.WithError(err).Error(err.Error())
|
||||
return
|
||||
}
|
||||
quantity = fixedpoint.NewFromFloat(qf)
|
||||
}
|
||||
|
||||
// for spot, we need to modify the quantity
|
||||
baseBalance, _ := session.Account.Balance(market.BaseCurrency)
|
||||
if s.MaxBaseAssetBalance > 0 && baseBalance.Total()+quantity > s.MaxBaseAssetBalance {
|
||||
quota := s.MaxBaseAssetBalance - baseBalance.Total()
|
||||
quantity = bbgo.AdjustQuantityByMaxAmount(quantity, closePrice, quota)
|
||||
}
|
||||
|
||||
quoteBalance, ok := session.Account.Balance(market.QuoteCurrency)
|
||||
if !ok {
|
||||
log.Errorf("quote balance %s not found", market.QuoteCurrency)
|
||||
return
|
||||
}
|
||||
|
||||
// for spot, we need to modify the quantity according to the quote balance
|
||||
if !session.Margin {
|
||||
minNotional := closePrice * 1.003 * quantity
|
||||
b, ok := session.Account.Balance(market.QuoteCurrency)
|
||||
if !ok {
|
||||
log.Errorf("balance %s not found", market.QuoteCurrency)
|
||||
return
|
||||
}
|
||||
// add 0.3% for price slippage
|
||||
notional := closePrice.Mul(quantity).MulFloat64(1.003)
|
||||
|
||||
if minNotional > b.Available.Float64() {
|
||||
log.Warnf("modifying quantity %f according to the min quote balance %f %s", quantity, b.Available.Float64(), market.QuoteCurrency)
|
||||
quantity = bbgo.AdjustFloatQuantityByMaxAmount(quantity, closePrice, b.Available.Float64())
|
||||
if s.MinQuoteAssetBalance > 0 && quoteBalance.Available-notional < s.MinQuoteAssetBalance {
|
||||
log.Warnf("modifying quantity %f according to the min quote asset balance %f %s",
|
||||
quantity.Float64(),
|
||||
quoteBalance.Available.Float64(),
|
||||
market.QuoteCurrency)
|
||||
quota := quoteBalance.Available - s.MinQuoteAssetBalance
|
||||
quantity = bbgo.AdjustQuantityByMinAmount(quantity, closePrice, quota)
|
||||
} else if notional > quoteBalance.Available {
|
||||
log.Warnf("modifying quantity %f according to the quote asset balance %f %s",
|
||||
quantity.Float64(),
|
||||
quoteBalance.Available.Float64(),
|
||||
market.QuoteCurrency)
|
||||
quantity = bbgo.AdjustQuantityByMaxAmount(quantity, closePrice, quoteBalance.Available)
|
||||
}
|
||||
}
|
||||
|
||||
s.Notify("Submitting %s market order buy with quantity %f according to the support volume %f", s.Symbol, quantity, kline.Volume)
|
||||
s.Notify("Submitting %s market order buy with quantity %f according to the base volume %f, taker buy base volume %f",
|
||||
s.Symbol,
|
||||
quantity,
|
||||
kline.Volume,
|
||||
kline.TakerBuyBaseAssetVolume)
|
||||
|
||||
orderForm := types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Market: market,
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: quantity,
|
||||
Quantity: quantity.Float64(),
|
||||
MarginSideEffect: s.MarginOrderSideEffect,
|
||||
}
|
||||
|
||||
_, err := orderExecutor.SubmitOrders(ctx, orderForm)
|
||||
createdOrders, err := orderExecutor.SubmitOrders(ctx, orderForm)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("submit order error")
|
||||
return
|
||||
}
|
||||
s.orderStore.Add(createdOrders...)
|
||||
|
||||
// submit target orders
|
||||
var targetOrders []types.SubmitOrder
|
||||
for _, target := range s.Targets {
|
||||
targetPrice := closePrice * (1.0 + target.ProfitPercentage)
|
||||
targetQuantity := quantity * target.QuantityPercentage
|
||||
targetPrice := closePrice.Float64() * (1.0 + target.ProfitPercentage)
|
||||
targetQuantity := quantity.Float64() * target.QuantityPercentage
|
||||
targetQuoteQuantity := targetPrice * targetQuantity
|
||||
|
||||
if targetQuoteQuantity <= market.MinNotional {
|
||||
|
|
Loading…
Reference in New Issue
Block a user