mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
schedule: refactor and improve schedule strategy with QuantityOrAmount struct
This commit is contained in:
parent
11bbdb16a0
commit
bed03dbd17
|
@ -1,4 +1,15 @@
|
|||
---
|
||||
# time godotenv -f .env.local -- go run ./cmd/bbgo backtest --exchange binance --base-asset-baseline --config config/schedule-ethusdt.yaml -v
|
||||
backtest:
|
||||
startTime: "2021-08-01"
|
||||
endTime: "2021-08-07"
|
||||
symbols:
|
||||
- ETHUSDT
|
||||
account:
|
||||
balances:
|
||||
ETH: 0.0
|
||||
USDT: 20_000.0
|
||||
|
||||
riskControls:
|
||||
# This is the session-based risk controller, which let you configure different risk controller by session.
|
||||
sessionBased:
|
||||
|
@ -18,16 +29,17 @@ riskControls:
|
|||
|
||||
exchangeStrategies:
|
||||
|
||||
- on: max
|
||||
- on: binance
|
||||
schedule:
|
||||
# trigger schedule per hour
|
||||
# valid intervals are: 1m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
|
||||
interval: 1h
|
||||
|
||||
|
||||
symbol: ETHUSDT
|
||||
side: buy
|
||||
quantity: 0.01
|
||||
|
||||
# quantity: 0.01
|
||||
amount: 11.0
|
||||
|
||||
# execute order only when the closed price is below the moving average line.
|
||||
# you can open the app to adjust your parameters here.
|
||||
# the interval here could be different from the triggering interval.
|
||||
|
@ -35,3 +47,6 @@ exchangeStrategies:
|
|||
type: EWMA
|
||||
interval: 1h
|
||||
window: 99
|
||||
# quantity: 0.05
|
||||
amount: 11.0
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package bbgo
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -12,8 +12,8 @@ type MovingAverageSettings struct {
|
|||
Window int `json:"window"`
|
||||
|
||||
Side *types.SideType `json:"side"`
|
||||
Quantity *fixedpoint.Value `json:"quantity"`
|
||||
Amount *fixedpoint.Value `json:"amount"`
|
||||
|
||||
QuantityOrAmount
|
||||
}
|
||||
|
||||
func (settings MovingAverageSettings) IntervalWindow() types.IntervalWindow {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package bbgo
|
||||
|
||||
import "github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
// QuantityOrAmount is a setting structure used for quantity/amount settings
|
||||
// You can embed this struct into your strategy to share the setting methods
|
||||
|
@ -10,7 +14,18 @@ type QuantityOrAmount struct {
|
|||
Quantity fixedpoint.Value `json:"quantity"`
|
||||
|
||||
// Amount is the order quote amount for your buy/sell order.
|
||||
Amount fixedpoint.Value `json:"amount"`
|
||||
Amount fixedpoint.Value `json:"amount,omitempty"`
|
||||
}
|
||||
|
||||
func (qa *QuantityOrAmount) IsSet() bool {
|
||||
return qa.Quantity > 0 || qa.Amount > 0
|
||||
}
|
||||
|
||||
func (qa *QuantityOrAmount) Validate() error {
|
||||
if qa.Quantity == 0 && qa.Amount == 0 {
|
||||
return errors.New("either quantity or amount can not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateQuantity calculates the equivalent quantity of the given price when amount is set
|
||||
|
|
|
@ -2,10 +2,12 @@ package schedule
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -16,7 +18,6 @@ func init() {
|
|||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
|
||||
type Strategy struct {
|
||||
Market types.Market
|
||||
|
||||
|
@ -33,12 +34,9 @@ type Strategy struct {
|
|||
Symbol string `json:"symbol"`
|
||||
|
||||
// Side is the order side type, which can be buy or sell
|
||||
Side types.SideType `json:"side"`
|
||||
Side types.SideType `json:"side,omitempty"`
|
||||
|
||||
// Quantity is the quantity of the submit order
|
||||
Quantity fixedpoint.Value `json:"quantity,omitempty"`
|
||||
|
||||
Amount fixedpoint.Value `json:"amount,omitempty"`
|
||||
bbgo.QuantityOrAmount
|
||||
|
||||
BelowMovingAverage *bbgo.MovingAverageSettings `json:"belowMovingAverage,omitempty"`
|
||||
|
||||
|
@ -60,8 +58,8 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
}
|
||||
|
||||
func (s *Strategy) Validate() error {
|
||||
if s.Quantity == 0 && s.Amount == 0 {
|
||||
return errors.New("either quantity or amount can not be empty")
|
||||
if err := s.QuantityOrAmount.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -99,9 +97,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
|
||||
closePrice := fixedpoint.NewFromFloat(kline.Close)
|
||||
quantity := s.Quantity
|
||||
amount := s.Amount
|
||||
|
||||
quantity := s.QuantityOrAmount.CalculateQuantity(closePrice)
|
||||
side := s.Side
|
||||
|
||||
if s.BelowMovingAverage != nil || s.AboveMovingAverage != nil {
|
||||
|
@ -116,12 +112,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
|
||||
// override the default quantity or amount
|
||||
if s.BelowMovingAverage.Quantity != nil {
|
||||
quantity = *s.BelowMovingAverage.Quantity
|
||||
} else if s.BelowMovingAverage.Amount != nil {
|
||||
amount = *s.BelowMovingAverage.Amount
|
||||
if s.BelowMovingAverage.QuantityOrAmount.IsSet() {
|
||||
quantity = s.BelowMovingAverage.QuantityOrAmount.CalculateQuantity(closePrice)
|
||||
}
|
||||
|
||||
}
|
||||
} else if aboveMA != nil && closePrice.Float64() > aboveMA.Last() {
|
||||
match = true
|
||||
|
@ -130,11 +123,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
side = *s.AboveMovingAverage.Side
|
||||
}
|
||||
|
||||
// override the default quantity or amount
|
||||
if s.AboveMovingAverage.Quantity != nil {
|
||||
quantity = *s.AboveMovingAverage.Quantity
|
||||
} else if s.AboveMovingAverage.Amount != nil {
|
||||
amount = *s.AboveMovingAverage.Amount
|
||||
if s.AboveMovingAverage.QuantityOrAmount.IsSet() {
|
||||
quantity = s.AboveMovingAverage.QuantityOrAmount.CalculateQuantity(closePrice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,11 +135,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
}
|
||||
}
|
||||
|
||||
// convert amount to quantity if amount is given
|
||||
if amount > 0 {
|
||||
quantity = amount.Div(closePrice)
|
||||
}
|
||||
|
||||
// calculate quote quantity for balance checking
|
||||
quoteQuantity := quantity.Mul(closePrice)
|
||||
|
||||
|
@ -158,35 +143,42 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
case types.SideTypeBuy:
|
||||
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
|
||||
if !ok {
|
||||
log.Errorf("can not place scheduled %s order, quote balance %s is empty", s.Symbol, s.Market.QuoteCurrency)
|
||||
return
|
||||
}
|
||||
|
||||
if quoteBalance.Available < quoteQuantity {
|
||||
s.Notifiability.Notify("Quote balance %s is not enough: %f < %f", s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity.Float64())
|
||||
s.Notifiability.Notify("Can not place scheduled %s order: quote balance %s is not enough: %f < %f", s.Symbol, s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity.Float64())
|
||||
log.Errorf("can not place scheduled %s order: quote balance %s is not enough: %f < %f", s.Symbol, s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity.Float64())
|
||||
return
|
||||
}
|
||||
|
||||
case types.SideTypeSell:
|
||||
baseBalance, ok := session.Account.Balance(s.Market.BaseCurrency)
|
||||
if !ok {
|
||||
log.Errorf("can not place scheduled %s order, base balance %s is empty", s.Symbol, s.Market.BaseCurrency)
|
||||
return
|
||||
}
|
||||
|
||||
if baseBalance.Available < quantity {
|
||||
s.Notifiability.Notify("Base balance %s is not enough: %f < %f", s.Market.QuoteCurrency, baseBalance.Available.Float64(), quantity.Float64())
|
||||
s.Notifiability.Notify("Can not place scheduled %s order: base balance %s is not enough: %f < %f", s.Symbol, s.Market.QuoteCurrency, baseBalance.Available.Float64(), quantity.Float64())
|
||||
log.Errorf("can not place scheduled %s order: base balance %s is not enough: %f < %f", s.Symbol, s.Market.QuoteCurrency, baseBalance.Available.Float64(), quantity.Float64())
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
s.Notifiability.Notify("Submitting scheduled order %s quantity %f at price %f", s.Symbol, quantity.Float64(), closePrice.Float64())
|
||||
s.Notifiability.Notify("Submitting scheduled %s order with quantity %f at price %f", s.Symbol, quantity.Float64(), closePrice.Float64())
|
||||
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: side,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: quantity.Float64(),
|
||||
Market: s.Market,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("submit order error")
|
||||
s.Notifiability.Notify("Can not place scheduled %s order: submit error %s", s.Symbol, err.Error())
|
||||
log.WithError(err).Errorf("can not place scheduled %s order error", s.Symbol)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user