Merge pull request #268 from c9s/strategy/etf

feature: add ETF strategy
This commit is contained in:
Yo-An Lin 2021-08-26 11:38:56 +08:00 committed by GitHub
commit 6210fe2262
5 changed files with 168 additions and 44 deletions

11
config/etf.yaml Normal file
View File

@ -0,0 +1,11 @@
exchangeStrategies:
- on: max
etf:
duration: 24h
totalAmount: 200.0
index:
BTCUSDT: 5%
LTCUSDT: 15%
ETHUSDT: 30%
LINKUSDT: 20%
DOTUSDT: 30%

View File

@ -0,0 +1,47 @@
package bbgo
import (
"fmt"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/strategy/schedule"
"github.com/c9s/bbgo/pkg/types"
)
type MovingAverageSettings struct {
Type string `json:"type"`
Interval types.Interval `json:"interval"`
Window int `json:"window"`
Side *types.SideType `json:"side"`
Quantity *fixedpoint.Value `json:"quantity"`
Amount *fixedpoint.Value `json:"amount"`
}
func (settings MovingAverageSettings) IntervalWindow() types.IntervalWindow {
var window = 99
if settings.Window > 0 {
window = settings.Window
}
return types.IntervalWindow{
Interval: settings.Interval,
Window: window,
}
}
func (settings *MovingAverageSettings) Indicator(indicatorSet *StandardIndicatorSet) (inc schedule.Float64Indicator, err error) {
var iw = settings.IntervalWindow()
switch settings.Type {
case "SMA":
inc = indicatorSet.SMA(iw)
case "EWMA", "EMA":
inc = indicatorSet.EWMA(iw)
default:
return nil, fmt.Errorf("unsupported moving average type: %s", settings.Type)
}
return inc, nil
}

View File

@ -4,6 +4,7 @@ package cmd
import (
_ "github.com/c9s/bbgo/pkg/strategy/bollgrid"
_ "github.com/c9s/bbgo/pkg/strategy/buyandhold"
_ "github.com/c9s/bbgo/pkg/strategy/etf"
_ "github.com/c9s/bbgo/pkg/strategy/flashcrash"
_ "github.com/c9s/bbgo/pkg/strategy/gap"
_ "github.com/c9s/bbgo/pkg/strategy/grid"

View File

@ -0,0 +1,106 @@
package etf
import (
"context"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"time"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/types"
)
const ID = "etf"
func init() {
bbgo.RegisterStrategy(ID, &Strategy{})
}
type Strategy struct {
Market types.Market
Notifiability *bbgo.Notifiability
TotalAmount fixedpoint.Value `json:"totalAmount,omitempty"`
// Interval is the period that you want to submit order
Duration types.Duration `json:"duration"`
Index map[string]fixedpoint.Value `json:"index"`
}
func (s *Strategy) ID() string {
return ID
}
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
}
func (s *Strategy) Validate() error {
if s.TotalAmount == 0 {
return errors.New("amount can not be empty")
}
return nil
}
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
go func() {
ticker := time.NewTicker(s.Duration.Duration())
defer ticker.Stop()
s.Notifiability.Notify("ETF orders will be executed every %s", s.Duration.Duration().String())
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
totalAmount := s.TotalAmount
for symbol, ratio := range s.Index {
amount := totalAmount.Mul(ratio)
ticker, err := session.Exchange.QueryTicker(ctx, symbol)
if err != nil {
log.WithError(err).Error("query ticker error")
}
askPrice := fixedpoint.NewFromFloat(ticker.Sell)
quantity := askPrice.Div(amount)
// execute orders
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
if !ok {
return
}
if quoteBalance.Available < amount {
s.Notifiability.Notify("Quote balance %s is not enough: %f < %f", s.Market.QuoteCurrency, quoteBalance.Available.Float64(), amount.Float64())
return
}
s.Notifiability.Notify("Submitting etf order %s quantity %f at price %f (index ratio %f %%)",
symbol,
quantity.Float64(),
askPrice.Float64(),
ratio.Float64()*100.0)
_, err = orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeMarket,
Quantity: quantity.Float64(),
})
if err != nil {
log.WithError(err).Error("submit order error")
}
}
}
}
}()
return nil
}

View File

@ -2,8 +2,6 @@ package schedule
import (
"context"
"fmt"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@ -23,45 +21,6 @@ type Float64Indicator interface {
Last() float64
}
type MovingAverageSettings struct {
Type string `json:"type"`
Interval types.Interval `json:"interval"`
Window int `json:"window"`
Side *types.SideType `json:"side"`
Quantity *fixedpoint.Value `json:"quantity"`
Amount *fixedpoint.Value `json:"amount"`
}
func (settings MovingAverageSettings) IntervalWindow() types.IntervalWindow {
var window = 99
if settings.Window > 0 {
window = settings.Window
}
return types.IntervalWindow{
Interval: settings.Interval,
Window: window,
}
}
func (settings *MovingAverageSettings) Indicator(indicatorSet *bbgo.StandardIndicatorSet) (inc Float64Indicator, err error) {
var iw = settings.IntervalWindow()
switch settings.Type {
case "SMA":
inc = indicatorSet.SMA(iw)
case "EWMA", "EMA":
inc = indicatorSet.EWMA(iw)
default:
return nil, fmt.Errorf("unsupported moving average type: %s", settings.Type)
}
return inc, nil
}
type Strategy struct {
Market types.Market
@ -85,9 +44,9 @@ type Strategy struct {
Amount fixedpoint.Value `json:"amount,omitempty"`
BelowMovingAverage *MovingAverageSettings `json:"belowMovingAverage,omitempty"`
BelowMovingAverage *bbgo.MovingAverageSettings `json:"belowMovingAverage,omitempty"`
AboveMovingAverage *MovingAverageSettings `json:"aboveMovingAverage,omitempty"`
AboveMovingAverage *bbgo.MovingAverageSettings `json:"aboveMovingAverage,omitempty"`
}
func (s *Strategy) ID() string {
@ -218,7 +177,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
s.Notifiability.Notify("Submitting scheduled order %s quantity %f", s.Symbol, quantity.Float64())
s.Notifiability.Notify("Submitting scheduled order %s quantity %f at price %f", s.Symbol, quantity.Float64(), closePrice.Float64())
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol,
Side: side,