mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
add rsicross strategy
This commit is contained in:
parent
7c2de46273
commit
5c88abe72f
53
config/rsicross.yaml
Normal file
53
config/rsicross.yaml
Normal file
|
@ -0,0 +1,53 @@
|
|||
persistence:
|
||||
json:
|
||||
directory: var/data
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
db: 0
|
||||
|
||||
sessions:
|
||||
binance:
|
||||
exchange: binance
|
||||
envVarPrefix: binance
|
||||
|
||||
exchangeStrategies:
|
||||
- on: binance
|
||||
rsicross:
|
||||
symbol: BTCUSDT
|
||||
interval: 5m
|
||||
fastWindow: 7
|
||||
slowWindow: 12
|
||||
|
||||
quantity: 0.1
|
||||
|
||||
### RISK CONTROLS
|
||||
## circuitBreakEMA is used for calculating the price for circuitBreak
|
||||
# circuitBreakEMA:
|
||||
# interval: 1m
|
||||
# window: 14
|
||||
|
||||
## circuitBreakLossThreshold is the maximum loss threshold for realized+unrealized PnL
|
||||
# circuitBreakLossThreshold: -10.0
|
||||
|
||||
## positionHardLimit is the maximum position limit
|
||||
# positionHardLimit: 500.0
|
||||
|
||||
## maxPositionQuantity is the maximum quantity per order that could be controlled in positionHardLimit,
|
||||
## this parameter is used with positionHardLimit togerther
|
||||
# maxPositionQuantity: 10.0
|
||||
|
||||
backtest:
|
||||
startTime: "2022-01-01"
|
||||
endTime: "2022-02-01"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
sessions: [binance]
|
||||
# syncSecKLines: true
|
||||
accounts:
|
||||
binance:
|
||||
makerFeeRate: 0.0%
|
||||
takerFeeRate: 0.075%
|
||||
balances:
|
||||
BTC: 0.0
|
||||
USDT: 10000.0
|
|
@ -417,6 +417,8 @@ func (e *GeneralOrderExecutor) OpenPosition(ctx context.Context, options OpenPos
|
|||
return createdOrder, nil
|
||||
}
|
||||
|
||||
log.WithError(err).Errorf("unable to submit order: %v", err)
|
||||
log.Infof("reduce quantity and retry order")
|
||||
return e.reduceQuantityAndSubmitOrder(ctx, price, *submitOrder)
|
||||
}
|
||||
|
||||
|
|
|
@ -222,7 +222,6 @@ func (trader *Trader) injectFieldsAndSubscribe(ctx context.Context) error {
|
|||
// load and run Session strategies
|
||||
for sessionName, strategies := range trader.exchangeStrategies {
|
||||
var session = trader.environment.sessions[sessionName]
|
||||
var orderExecutor = trader.getSessionOrderExecutor(sessionName)
|
||||
for _, strategy := range strategies {
|
||||
rs := reflect.ValueOf(strategy)
|
||||
|
||||
|
@ -237,10 +236,6 @@ func (trader *Trader) injectFieldsAndSubscribe(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := dynamic.InjectField(rs, "OrderExecutor", orderExecutor, false); err != nil {
|
||||
return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy)
|
||||
}
|
||||
|
||||
if defaulter, ok := strategy.(StrategyDefaulter); ok {
|
||||
if err := defaulter.Defaults(); err != nil {
|
||||
panic(err)
|
||||
|
@ -441,7 +436,7 @@ func (trader *Trader) injectCommonServices(ctx context.Context, s interface{}) e
|
|||
return fmt.Errorf("field Persistence is not a struct element, %s given", field)
|
||||
}
|
||||
|
||||
if err := dynamic.InjectField(elem, "Facade", ps, true); err != nil {
|
||||
if err := dynamic.InjectField(elem.Interface(), "Facade", ps, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/pricealert"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/pricedrop"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/rebalance"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/rsicross"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/rsmaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/schedule"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/scmaker"
|
||||
|
|
|
@ -3,22 +3,19 @@ package dynamic
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
type testEnvironment struct {
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func InjectField(rs reflect.Value, fieldName string, obj interface{}, pointerOnly bool) error {
|
||||
func InjectField(target interface{}, fieldName string, obj interface{}, pointerOnly bool) error {
|
||||
rs := reflect.ValueOf(target)
|
||||
field := rs.FieldByName(fieldName)
|
||||
|
||||
if !field.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
@ -131,96 +128,3 @@ func ParseStructAndInject(f interface{}, objects ...interface{}) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_injectField(t *testing.T) {
|
||||
type TT struct {
|
||||
TradeService *service.TradeService
|
||||
}
|
||||
|
||||
// only pointer object can be set.
|
||||
var tt = &TT{}
|
||||
|
||||
// get the value of the pointer, or it can not be set.
|
||||
var rv = reflect.ValueOf(tt).Elem()
|
||||
|
||||
_, ret := HasField(rv, "TradeService")
|
||||
assert.True(t, ret)
|
||||
|
||||
ts := &service.TradeService{}
|
||||
|
||||
err := InjectField(rv, "TradeService", ts, true)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_parseStructAndInject(t *testing.T) {
|
||||
t.Run("skip nil", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, ss.Env)
|
||||
})
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &testEnvironment{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Env)
|
||||
})
|
||||
|
||||
t.Run("composition", func(t *testing.T) {
|
||||
type TT struct {
|
||||
*service.TradeService
|
||||
}
|
||||
ss := TT{}
|
||||
err := ParseStructAndInject(&ss, &service.TradeService{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.TradeService)
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, testEnvironment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, time.Time{}, ss.Env.startTime)
|
||||
})
|
||||
t.Run("interface/any", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Any interface{} // anything
|
||||
}{
|
||||
Any: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &testEnvironment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Any)
|
||||
})
|
||||
t.Run("interface/stringer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Stringer types.Stringer // stringer interface
|
||||
}{
|
||||
Stringer: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &types.Trade{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Stringer)
|
||||
})
|
||||
}
|
||||
|
|
105
pkg/dynamic/inject_test.go
Normal file
105
pkg/dynamic/inject_test.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func Test_injectField(t *testing.T) {
|
||||
type TT struct {
|
||||
TradeService *service.TradeService
|
||||
}
|
||||
|
||||
// only pointer object can be set.
|
||||
var tt = &TT{}
|
||||
|
||||
// get the value of the pointer, or it can not be set.
|
||||
var rv = reflect.ValueOf(tt).Elem()
|
||||
|
||||
_, ret := HasField(rv, "TradeService")
|
||||
assert.True(t, ret)
|
||||
|
||||
ts := &service.TradeService{}
|
||||
|
||||
err := InjectField(rv, "TradeService", ts, true)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_parseStructAndInject(t *testing.T) {
|
||||
t.Run("skip nil", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, ss.Env)
|
||||
})
|
||||
t.Run("pointer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env *testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
Env: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &testEnvironment{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Env)
|
||||
})
|
||||
|
||||
t.Run("composition", func(t *testing.T) {
|
||||
type TT struct {
|
||||
*service.TradeService
|
||||
}
|
||||
ss := TT{}
|
||||
err := ParseStructAndInject(&ss, &service.TradeService{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.TradeService)
|
||||
})
|
||||
|
||||
t.Run("struct", func(t *testing.T) {
|
||||
ss := struct {
|
||||
a int
|
||||
Env testEnvironment
|
||||
}{
|
||||
a: 1,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, testEnvironment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, time.Time{}, ss.Env.startTime)
|
||||
})
|
||||
t.Run("interface/any", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Any interface{} // anything
|
||||
}{
|
||||
Any: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &testEnvironment{
|
||||
startTime: time.Now(),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Any)
|
||||
})
|
||||
t.Run("interface/stringer", func(t *testing.T) {
|
||||
ss := struct {
|
||||
Stringer types.Stringer // stringer interface
|
||||
}{
|
||||
Stringer: nil,
|
||||
}
|
||||
err := ParseStructAndInject(&ss, &types.Trade{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ss.Stringer)
|
||||
})
|
||||
}
|
|
@ -20,7 +20,7 @@ type Strategy struct {
|
|||
OrderExecutor *bbgo.GeneralOrderExecutor
|
||||
}
|
||||
|
||||
func (s *Strategy) Setup(ctx context.Context, environ *bbgo.Environment, session *bbgo.ExchangeSession, market types.Market, strategyID, instanceID string) {
|
||||
func (s *Strategy) Initialize(ctx context.Context, environ *bbgo.Environment, session *bbgo.ExchangeSession, market types.Market, strategyID, instanceID string) {
|
||||
s.parent = ctx
|
||||
s.ctx, s.cancel = context.WithCancel(ctx)
|
||||
|
||||
|
@ -53,6 +53,6 @@ func (s *Strategy) Setup(ctx context.Context, environ *bbgo.Environment, session
|
|||
s.OrderExecutor.BindProfitStats(s.ProfitStats)
|
||||
s.OrderExecutor.Bind()
|
||||
s.OrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||
bbgo.Sync(ctx, s)
|
||||
// bbgo.Sync(ctx, s)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@ package linregmaker
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/risk/dynamicrisk"
|
||||
"sync"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/risk/dynamicrisk"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
|
||||
|
@ -221,20 +222,20 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
})
|
||||
}
|
||||
|
||||
// Setup Exits
|
||||
// Initialize Exits
|
||||
s.ExitMethods.SetAndSubscribe(session, s)
|
||||
|
||||
// Setup dynamic spread
|
||||
// Initialize dynamic spread
|
||||
if s.DynamicSpread.IsEnabled() {
|
||||
s.DynamicSpread.Initialize(s.Symbol, session)
|
||||
}
|
||||
|
||||
// Setup dynamic exposure
|
||||
// Initialize dynamic exposure
|
||||
if s.DynamicExposure.IsEnabled() {
|
||||
s.DynamicExposure.Initialize(s.Symbol, session)
|
||||
}
|
||||
|
||||
// Setup dynamic quantities
|
||||
// Initialize dynamic quantities
|
||||
if len(s.DynamicQuantityIncrease) > 0 {
|
||||
s.DynamicQuantityIncrease.Initialize(s.Symbol, session)
|
||||
}
|
||||
|
|
137
pkg/strategy/rsicross/strategy.go
Normal file
137
pkg/strategy/rsicross/strategy.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package rsicross
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator"
|
||||
"github.com/c9s/bbgo/pkg/risk/riskcontrol"
|
||||
"github.com/c9s/bbgo/pkg/strategy/common"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
const ID = "rsicross"
|
||||
|
||||
func init() {
|
||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
*common.Strategy
|
||||
|
||||
Environment *bbgo.Environment
|
||||
Market types.Market
|
||||
|
||||
Symbol string `json:"symbol"`
|
||||
Interval types.Interval `json:"interval"`
|
||||
SlowWindow int `json:"slowWindow"`
|
||||
FastWindow int `json:"fastWindow"`
|
||||
|
||||
bbgo.OpenPositionOptions
|
||||
|
||||
// risk related parameters
|
||||
PositionHardLimit fixedpoint.Value `json:"positionHardLimit"`
|
||||
MaxPositionQuantity fixedpoint.Value `json:"maxPositionQuantity"`
|
||||
CircuitBreakLossThreshold fixedpoint.Value `json:"circuitBreakLossThreshold"`
|
||||
CircuitBreakEMA types.IntervalWindow `json:"circuitBreakEMA"`
|
||||
|
||||
positionRiskControl *riskcontrol.PositionRiskControl
|
||||
circuitBreakRiskControl *riskcontrol.CircuitBreakRiskControl
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
return ID
|
||||
}
|
||||
|
||||
func (s *Strategy) InstanceID() string {
|
||||
return fmt.Sprintf("%s:%s:%s:%d-%d", ID, s.Symbol, s.Interval, s.FastWindow, s.SlowWindow)
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
s.Strategy = &common.Strategy{}
|
||||
s.Strategy.Initialize(ctx, s.Environment, session, s.Market, ID, s.InstanceID())
|
||||
|
||||
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
|
||||
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
|
||||
s.positionRiskControl = riskcontrol.NewPositionRiskControl(s.OrderExecutor, s.PositionHardLimit, s.MaxPositionQuantity)
|
||||
}
|
||||
|
||||
if !s.CircuitBreakLossThreshold.IsZero() {
|
||||
log.Infof("circuitBreakLossThreshold is configured, setting up CircuitBreakRiskControl...")
|
||||
s.circuitBreakRiskControl = riskcontrol.NewCircuitBreakRiskControl(
|
||||
s.Position,
|
||||
session.Indicators(s.Symbol).EWMA(s.CircuitBreakEMA),
|
||||
s.CircuitBreakLossThreshold,
|
||||
s.ProfitStats)
|
||||
}
|
||||
|
||||
fastRsi := session.Indicators(s.Symbol).RSI(types.IntervalWindow{Interval: s.Interval, Window: s.FastWindow})
|
||||
slowRsi := session.Indicators(s.Symbol).RSI(types.IntervalWindow{Interval: s.Interval, Window: s.SlowWindow})
|
||||
rsiCross := indicator.Cross(fastRsi, slowRsi)
|
||||
rsiCross.OnUpdate(func(v float64) {
|
||||
switch indicator.CrossType(v) {
|
||||
case indicator.CrossOver:
|
||||
opts := s.OpenPositionOptions
|
||||
opts.Long = true
|
||||
|
||||
if price, ok := session.LastPrice(s.Symbol); ok {
|
||||
opts.Price = price
|
||||
}
|
||||
|
||||
// opts.Price = closePrice
|
||||
opts.Tags = []string{"rsiCrossOver"}
|
||||
if _, err := s.OrderExecutor.OpenPosition(ctx, opts); err != nil {
|
||||
logErr(err, "unable to open position")
|
||||
}
|
||||
|
||||
case indicator.CrossUnder:
|
||||
if err := s.OrderExecutor.ClosePosition(ctx, fixedpoint.One); err != nil {
|
||||
logErr(err, "failed to close position")
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) preloadKLines(inc *indicator.KLineStream, session *bbgo.ExchangeSession, symbol string, interval types.Interval) {
|
||||
if store, ok := session.MarketDataStore(symbol); ok {
|
||||
if kLinesData, ok := store.KLinesOfInterval(interval); ok {
|
||||
for _, k := range *kLinesData {
|
||||
inc.EmitUpdate(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logErr(err error, msgAndArgs ...interface{}) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(msgAndArgs) == 0 {
|
||||
log.WithError(err).Error(err.Error())
|
||||
} else if len(msgAndArgs) == 1 {
|
||||
msg := msgAndArgs[0].(string)
|
||||
log.WithError(err).Error(msg)
|
||||
} else if len(msgAndArgs) > 1 {
|
||||
msg := msgAndArgs[0].(string)
|
||||
log.WithError(err).Errorf(msg, msgAndArgs[1:]...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -101,7 +101,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
|||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
s.Strategy = &common.Strategy{}
|
||||
s.Strategy.Setup(ctx, s.Environment, session, s.Market, ID, s.InstanceID())
|
||||
s.Strategy.Initialize(ctx, s.Environment, session, s.Market, ID, s.InstanceID())
|
||||
|
||||
s.book = types.NewStreamBook(s.Symbol)
|
||||
s.book.BindStream(session.UserDataStream)
|
||||
|
|
Loading…
Reference in New Issue
Block a user