fix: enable interval parsing for non-whitelisted time spans

This commit is contained in:
zenix 2022-09-06 14:26:17 +09:00
parent 6c8902dd9c
commit 1d0893b699
7 changed files with 1143 additions and 38 deletions

View File

@ -56,12 +56,12 @@ exchangeStrategies:
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
#trailingActivationRatio: [0.01, 0.016, 0.05]
#trailingActivationRatio: [0.001, 0.0081, 0.022]
trailingActivationRatio: [0.0029, 0.028]
trailingActivationRatio: [0.0012, 0.01]
#trailingActivationRatio: []
#trailingCallbackRate: []
#trailingCallbackRate: [0.002, 0.01, 0.1]
#trailingCallbackRate: [0.0004, 0.0009, 0.018]
trailingCallbackRate: [0.0005, 0.0149]
trailingCallbackRate: [0.0006, 0.0049]
generateGraph: true
graphPNLDeductFee: false
@ -124,15 +124,15 @@ sync:
- BTCUSDT
backtest:
startTime: "2022-08-01"
endTime: "2022-08-31"
startTime: "2022-09-01"
endTime: "2022-09-15"
symbols:
- BTCUSDT
sessions: [binance]
accounts:
binance:
makerFeeRate: 0.000
#takerFeeRate: 0.000
takerFeeRate: 0.000
balances:
BTC: 0
USDT: 21

View File

@ -23,14 +23,14 @@ exchangeStrategies:
- on: binance
elliottwave:
symbol: BTCUSDT
symbol: BNBBUSD
# kline interval for indicators
interval: 4m
stoploss: 0.5%
interval: 3m
stoploss: 0.2%
windowATR: 14
windowQuick: 10
windowSlow: 96
source: hlc3
windowQuick: 5
windowSlow: 19
source: hl2
pendingMinutes: 10
useHeikinAshi: true
@ -44,12 +44,12 @@ exchangeStrategies:
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
#trailingActivationRatio: [0.01, 0.016, 0.05]
#trailingActivationRatio: [0.001, 0.0081, 0.022]
trailingActivationRatio: [0.0008, 0.0012, 0.0017, 0.01, 0.015]
trailingActivationRatio: [0.0017, 0.01, 0.015]
#trailingActivationRatio: []
#trailingCallbackRate: []
#trailingCallbackRate: [0.002, 0.01, 0.1]
#trailingCallbackRate: [0.0004, 0.0009, 0.018]
trailingCallbackRate: [0.0002, 0.0003, 0.0006, 0.0049, 0.006]
trailingCallbackRate: [0.0006, 0.0049, 0.006]
#exits:
# - roiStopLoss:
@ -105,18 +105,18 @@ sync:
sessions:
- binance
symbols:
- BTCUSDT
- BNBBUSD
backtest:
startTime: "2022-08-01"
startTime: "2022-09-01"
endTime: "2022-09-30"
symbols:
- BTCUSDT
- BNBBUSD
sessions: [binance]
accounts:
binance:
makerFeeRate: 0.000
takerFeeRate: 0.000
balances:
BTC: 0
USDT: 5000
BNB: 0
BUSD: 20

1060
pkg/strategy/drift/backup Normal file

File diff suppressed because it is too large Load Diff

View File

@ -125,9 +125,9 @@ func (s *Strategy) InstanceID() string {
}
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
Interval: s.Interval,
})
// by default, bbgo only pre-subscribe 1000 klines.
// this is not enough if we're subscribing 30m intervals using SerialMarketDataStore
bbgo.KLineLimit = int64((s.Interval.Minutes()*s.Window/1000 + 1) * 1000)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
Interval: types.Interval1m,
})
@ -173,7 +173,7 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
}
}
func (s *Strategy) initIndicators() error {
func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
s.ma = &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}}
s.stdevHigh = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}}
s.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}}
@ -207,7 +207,6 @@ func (s *Strategy) initIndicators() error {
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.ATRWindow}}
s.trendLine = &indicator.EWMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.TrendWindow}}
store, _ := s.Session.MarketDataStore(s.Symbol)
klines, ok := store.KLinesOfInterval(s.Interval)
klinesLength := len(*klines)
if !ok || klinesLength == 0 {
@ -243,7 +242,7 @@ func (s *Strategy) initIndicators() error {
if s.kline1m != nil && klines != nil {
s.kline1m.Set(&(*klines)[len(*klines)-1])
}
s.startTime = s.kline1m.EndTime.Time()
s.startTime = s.kline1m.StartTime.Time().Add(s.kline1m.Interval.Duration())
return nil
}
@ -257,7 +256,7 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, e
drift := s.drift1m.Array(2)
for _, order := range nonTraded {
log.Warnf("%v", order)
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.minutesCounter)
if s.minutesCounter-s.orderPendingCounter[order.OrderID] > s.PendingMinutes {
if order.Side == types.SideTypeBuy && drift[1] < drift[0] {
continue
@ -566,9 +565,6 @@ func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value {
}
func (s *Strategy) klineHandler1m(ctx context.Context, kline types.KLine) {
if s.Status != types.StrategyStatusRunning {
return
}
s.kline1m.Set(&kline)
s.drift1m.Update(s.GetSource(&kline).Float64(), kline.Volume.Float64())
if s.Status != types.StrategyStatusRunning {
@ -917,12 +913,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.frameKLine = &types.KLine{}
s.kline1m = &types.KLine{}
s.priceLines = types.NewQueue(300)
if err := s.initIndicators(); err != nil {
log.WithError(err).Errorf("initIndicator failed")
return nil
}
s.initTickerFunctions(ctx)
s.initTickerFunctions(ctx)
startTime := s.Environment.StartTime()
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1d, startTime))
s.TradeStats.SetIntervalProfitCollector(types.NewIntervalProfitCollector(types.Interval1w, startTime))
@ -993,8 +985,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if !ok {
panic("cannot get 1m history")
}
if err := s.initIndicators(store); err != nil {
log.WithError(err).Errorf("initIndicator failed")
return nil
}
store.OnKLineClosed(func(kline types.KLine) {
s.minutesCounter = int(kline.StartTime.Time().Sub(s.startTime).Minutes())
s.minutesCounter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Minutes())
if kline.Interval == types.Interval1m {
s.klineHandler1m(ctx, kline)
} else if kline.Interval == s.Interval {

View File

@ -100,7 +100,7 @@ func (s *Strategy) InstanceID() string {
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
// by default, bbgo only pre-subscribe 1000 klines.
// this is not enough if we're subscribing 30m intervals using SerialMarketDataStore
bbgo.KLineLimit = int64(s.Interval.Minutes()*s.WindowSlow + 1000)
bbgo.KLineLimit = int64((s.Interval.Minutes()*s.WindowSlow/1000 + 1) + 1000)
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{
Interval: types.Interval1m,
})
@ -159,7 +159,8 @@ func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error {
if !ok || klineLength == 0 {
return errors.New("klines not exists")
}
s.startTime = (*klines)[klineLength-1].EndTime.Time()
tmpK := (*klines)[klineLength-1]
s.startTime = tmpK.StartTime.Time().Add(tmpK.Interval.Duration())
s.heikinAshi = NewHeikinAshi(500)
for _, kline := range *klines {
@ -178,6 +179,7 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef float64) int {
if len(nonTraded) > 0 {
left := 0
for _, order := range nonTraded {
log.Warnf("%v | counter: %d, system: %d", order, s.orderPendingCounter[order.OrderID], s.minutesCounter)
toCancel := false
if s.minutesCounter-s.orderPendingCounter[order.OrderID] >= s.PendingMinutes {
toCancel = true
@ -364,7 +366,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
s.InitDrawCommands(store, &profit, &cumProfit)
store.OnKLineClosed(func(kline types.KLine) {
s.minutesCounter = int(kline.StartTime.Time().Sub(s.startTime).Minutes())
s.minutesCounter = int(kline.StartTime.Time().Add(kline.Interval.Duration()).Sub(s.startTime).Minutes())
if kline.Interval == types.Interval1m {
s.klineHandler1m(ctx, kline)
} else if kline.Interval == s.Interval {

View File

@ -3,13 +3,18 @@ package types
import (
"encoding/json"
"fmt"
"strings"
"time"
)
type Interval string
func (i Interval) Minutes() int {
return SupportedIntervals[i]
m, ok := SupportedIntervals[i]
if !ok {
return ParseInterval(i)
}
return m
}
func (i Interval) Duration() time.Duration {
@ -56,6 +61,34 @@ var Interval1w = Interval("1w")
var Interval2w = Interval("2w")
var Interval1mo = Interval("1mo")
func ParseInterval(input Interval) int {
t := 0
index := 0
for i, rn := range string(input) {
if rn >= '0' && rn <= '9' {
t = t*10 + int(rn-'0')
} else {
index = i
break
}
}
switch strings.ToLower(string(input[index:])) {
case "m":
return t
case "h":
t *= 60
case "d":
t *= 60 * 24
case "w":
t *= 60 * 24 * 7
case "mo":
t *= 60 * 24 * 30
default:
panic("unknown input: " + input)
}
return t
}
var SupportedIntervals = map[Interval]int{
Interval1m: 1,
Interval3m: 3,

View File

@ -0,0 +1,14 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseInterval(t *testing.T) {
assert.Equal(t, ParseInterval("3m"), 3)
assert.Equal(t, ParseInterval("15h"), 15*60)
assert.Equal(t, ParseInterval("72d"), 72*24*60)
assert.Equal(t, ParseInterval("3Mo"), 3*30*24*60)
}