feature: add plot for series. add autocorrelation. add clone for indicators/series

This commit is contained in:
zenix 2022-07-12 19:14:57 +09:00
parent 69b45e90e9
commit c51a99400d
18 changed files with 359 additions and 53 deletions

View File

@ -14,23 +14,24 @@ exchangeStrategies:
# kline interval for indicators # kline interval for indicators
interval: 15m interval: 15m
window: 3 window: 3
exits: stoploss: 2%
- roiStopLoss: #exits:
percentage: 0.8% #- roiStopLoss:
- roiTakeProfit: # percentage: 0.8%
percentage: 35% #- roiTakeProfit:
- protectiveStopLoss: # percentage: 35%
activationRatio: 0.6% #- protectiveStopLoss:
stopLossRatio: 0.1% # activationRatio: 0.6%
placeStopOrder: false # stopLossRatio: 0.1%
- protectiveStopLoss: # placeStopOrder: false
activationRatio: 5% #- protectiveStopLoss:
stopLossRatio: 1% # activationRatio: 5%
placeStopOrder: false # stopLossRatio: 1%
- cumulatedVolumeTakeProfit: # placeStopOrder: false
interval: 5m #- cumulatedVolumeTakeProfit:
window: 2 # interval: 5m
minQuoteVolume: 200_000_000 # window: 2
# minQuoteVolume: 200_000_000
#- protectiveStopLoss: #- protectiveStopLoss:
# activationRatio: 2% # activationRatio: 2%
# stopLossRatio: 1% # stopLossRatio: 1%
@ -53,8 +54,8 @@ backtest:
sessions: [binance] sessions: [binance]
accounts: accounts:
binance: binance:
#makerFeeRate: 0 #makerFeeRate: 0.00001
#takerFeeRate: 0 #takerFeeRate: 0.00001
balances: balances:
ETH: 0.0 ETH: 10.0
USDT: 5000.0 USDT: 5000.0

5
go.mod
View File

@ -2,7 +2,7 @@
module github.com/c9s/bbgo module github.com/c9s/bbgo
go 1.17 go 1.18
require ( require (
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
@ -43,6 +43,7 @@ require (
github.com/spf13/viper v1.7.1 github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/valyala/fastjson v1.5.1 github.com/valyala/fastjson v1.5.1
github.com/wcharczuk/go-chart/v2 v2.1.0
github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90 github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90
github.com/x-cray/logrus-prefixed-formatter v0.5.2 github.com/x-cray/logrus-prefixed-formatter v0.5.2
github.com/zserge/lorca v0.1.9 github.com/zserge/lorca v0.1.9
@ -75,6 +76,7 @@ require (
github.com/go-test/deep v1.0.6 // indirect github.com/go-test/deep v1.0.6 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
@ -117,6 +119,7 @@ require (
go.opentelemetry.io/otel/trace v0.19.0 // indirect go.opentelemetry.io/otel/trace v0.19.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/mod v0.5.1 // indirect golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect

3
go.sum
View File

@ -182,6 +182,7 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -516,6 +517,7 @@ github.com/ugorji/go/codec v1.2.3 h1:/mVYEV+Jo3IZKeA5gBngN0AvNnQltEDkR+eQikkWQu0
github.com/ugorji/go/codec v1.2.3/go.mod h1:5FxzDJIgeiWJZslYHPj+LS1dq1ZBQVelZFnjsFGI/Uc= github.com/ugorji/go/codec v1.2.3/go.mod h1:5FxzDJIgeiWJZslYHPj+LS1dq1ZBQVelZFnjsFGI/Uc=
github.com/valyala/fastjson v1.5.1 h1:SXaQZVSwLjZOVhDEhjiCcDtnX0Feu7Z7A1+C5atpoHM= github.com/valyala/fastjson v1.5.1 h1:SXaQZVSwLjZOVhDEhjiCcDtnX0Feu7Z7A1+C5atpoHM=
github.com/valyala/fastjson v1.5.1/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.5.1/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90 h1:G/O1RFjhc9hgVYjaPQ0Oceqxf3GwRQl/5XEAWYetjmg= github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90 h1:G/O1RFjhc9hgVYjaPQ0Oceqxf3GwRQl/5XEAWYetjmg=
github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90/go.mod h1:rpXAuuHgyEJb6kXcXldlkOjU6y4x+YcASKKXJNUhh0Y= github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90/go.mod h1:rpXAuuHgyEJb6kXcXldlkOjU6y4x+YcASKKXJNUhh0Y=
@ -584,6 +586,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=

View File

@ -22,6 +22,24 @@ type ATR struct {
var _ types.SeriesExtend = &ATR{} var _ types.SeriesExtend = &ATR{}
func (inc *ATR) Clone() *ATR {
out := &ATR{
IntervalWindow: inc.IntervalWindow,
PercentageVolatility: inc.PercentageVolatility[:],
PreviousClose: inc.PreviousClose,
RMA: inc.RMA.Clone().(*RMA),
EndTime: inc.EndTime,
}
out.SeriesBase.Series = out
return out
}
func (inc *ATR) TestUpdate(high, low, cloze float64) *ATR {
c := inc.Clone()
c.Update(high, low, cloze)
return c
}
func (inc *ATR) Update(high, low, cloze float64) { func (inc *ATR) Update(high, low, cloze float64) {
if inc.Window <= 0 { if inc.Window <= 0 {
panic("window must be greater than 0") panic("window must be greater than 0")

View File

@ -78,7 +78,6 @@ func (inc *CCI) Length() int {
var _ types.SeriesExtend = &CCI{} var _ types.SeriesExtend = &CCI{}
func (inc *CCI) PushK(k types.KLine) { func (inc *CCI) PushK(k types.KLine) {
inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64()) inc.Update(k.High.Add(k.Low).Add(k.Close).Div(three).Float64())
} }

View File

@ -9,4 +9,3 @@ import (
var three = fixedpoint.NewFromInt(3) var three = fixedpoint.NewFromInt(3)
var zeroTime = time.Time{} var zeroTime = time.Time{}

View File

@ -18,6 +18,23 @@ type DEMA struct {
UpdateCallbacks []func(value float64) UpdateCallbacks []func(value float64)
} }
func (inc *DEMA) Clone() *DEMA {
out := &DEMA{
IntervalWindow: inc.IntervalWindow,
Values: inc.Values[:],
a1: inc.a1.Clone(),
a2: inc.a2.Clone(),
}
out.SeriesBase.Series = out
return out
}
func (inc *DEMA) TestUpdate(value float64) *DEMA {
out := inc.Clone()
out.Update(value)
return out
}
func (inc *DEMA) Update(value float64) { func (inc *DEMA) Update(value float64) {
if len(inc.Values) == 0 { if len(inc.Values) == 0 {
inc.SeriesBase.Series = inc inc.SeriesBase.Series = inc

View File

@ -45,6 +45,24 @@ func (inc *Drift) Update(value float64) {
} }
} }
func (inc *Drift) Clone() (out *Drift) {
out = &Drift{
IntervalWindow: inc.IntervalWindow,
chng: inc.chng.Clone(),
Values: inc.Values[:],
SMA: inc.SMA.Clone().(*SMA),
LastValue: inc.LastValue,
}
out.SeriesBase.Series = out
return out
}
func (inc *Drift) TestUpdate(value float64) *Drift {
out := inc.Clone()
out.Update(value)
return out
}
func (inc *Drift) Index(i int) float64 { func (inc *Drift) Index(i int) float64 {
if inc.Values == nil { if inc.Values == nil {
return 0 return 0

View File

@ -23,6 +23,22 @@ type EWMA struct {
var _ types.SeriesExtend = &EWMA{} var _ types.SeriesExtend = &EWMA{}
func (inc *EWMA) Clone() *EWMA {
out := &EWMA{
IntervalWindow: inc.IntervalWindow,
Values: inc.Values[:],
LastOpenTime: inc.LastOpenTime,
}
out.SeriesBase.Series = out
return out
}
func (inc *EWMA) TestUpdate(value float64) *EWMA {
out := inc.Clone()
out.Update(value)
return out
}
func (inc *EWMA) Update(value float64) { func (inc *EWMA) Update(value float64) {
var multiplier = 2.0 / float64(1+inc.Window) var multiplier = 2.0 / float64(1+inc.Window)

View File

@ -25,6 +25,20 @@ type RMA struct {
updateCallbacks []func(value float64) updateCallbacks []func(value float64)
} }
func (inc *RMA) Clone() types.UpdatableSeriesExtend {
out := &RMA{
IntervalWindow: inc.IntervalWindow,
Values: inc.Values[:],
counter: inc.counter,
Adjust: inc.Adjust,
tmp: inc.tmp,
sum: inc.sum,
EndTime: inc.EndTime,
}
out.SeriesBase.Series = out
return out
}
func (inc *RMA) Update(x float64) { func (inc *RMA) Update(x float64) {
lambda := 1 / float64(inc.Window) lambda := 1 / float64(inc.Window)
if inc.counter == 0 { if inc.counter == 0 {

View File

@ -40,6 +40,16 @@ func (inc *SMA) Length() int {
return inc.Values.Length() return inc.Values.Length()
} }
func (inc *SMA) Clone() types.UpdatableSeriesExtend {
out := &SMA{
Values: inc.Values[:],
rawValues: types.Clone(inc.rawValues).(*types.Queue),
EndTime: inc.EndTime,
}
out.SeriesBase.Series = out
return out
}
var _ types.SeriesExtend = &SMA{} var _ types.SeriesExtend = &SMA{}
func (inc *SMA) Update(value float64) { func (inc *SMA) Update(value float64) {

View File

@ -1,2 +1 @@
package indicator package indicator

View File

@ -7,6 +7,7 @@ import (
"sync" "sync"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/wcharczuk/go-chart/v2"
"github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
@ -35,11 +36,13 @@ type Strategy struct {
*types.ProfitStats *types.ProfitStats
*types.TradeStats *types.TradeStats
drift types.UpdatableSeriesExtend drift *indicator.Drift
atr *indicator.ATR atr *indicator.ATR
midPrice fixedpoint.Value midPrice fixedpoint.Value
lock sync.RWMutex lock sync.RWMutex
stoploss float64 `json:"stoploss"`
ExitMethods bbgo.ExitMethodSet `json:"exits"` ExitMethods bbgo.ExitMethodSet `json:"exits"`
Session *bbgo.ExchangeSession Session *bbgo.ExchangeSession
*bbgo.GeneralOrderExecutor *bbgo.GeneralOrderExecutor
@ -69,6 +72,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
} }
var Three fixedpoint.Value = fixedpoint.NewFromInt(3) var Three fixedpoint.Value = fixedpoint.NewFromInt(3)
var Two fixedpoint.Value = fixedpoint.NewFromInt(2)
func (s *Strategy) GetLastPrice() (lastPrice fixedpoint.Value) { func (s *Strategy) GetLastPrice() (lastPrice fixedpoint.Value) {
var ok bool var ok bool
@ -98,6 +102,9 @@ var Delta fixedpoint.Value = fixedpoint.NewFromFloat(0.01)
func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) { func (s *Strategy) ClosePosition(ctx context.Context) (*types.Order, bool) {
order := s.Position.NewMarketCloseOrder(fixedpoint.One) order := s.Position.NewMarketCloseOrder(fixedpoint.One)
if order == nil {
return nil, false
}
order.TimeInForce = "" order.TimeInForce = ""
balances := s.Session.GetAccount().Balances() balances := s.Session.GetAccount().Balances()
baseBalance := balances[s.Market.BaseCurrency].Available baseBalance := balances[s.Market.BaseCurrency].Available
@ -133,7 +140,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
if s.TradeStats == nil { if s.TradeStats == nil {
s.TradeStats = &types.TradeStats{} s.TradeStats = types.NewTradeStats(s.Symbol)
} }
// StrategyController // StrategyController
@ -165,9 +172,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
store, _ := session.MarketDataStore(s.Symbol) store, _ := session.MarketDataStore(s.Symbol)
getSource := func(kline *types.KLine) fixedpoint.Value {
//return kline.High.Add(kline.Low).Div(Two)
//return kline.Close
return kline.High.Add(kline.Low).Add(kline.Close).Div(Three)
}
s.drift = &indicator.Drift{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.Window}} s.drift = &indicator.Drift{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.Window}}
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 34}} s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 14}}
s.atr.Bind(store)
klines, ok := store.KLinesOfInterval(s.Interval) klines, ok := store.KLinesOfInterval(s.Interval)
if !ok { if !ok {
@ -175,7 +187,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
return nil return nil
} }
for _, kline := range *klines { for _, kline := range *klines {
s.drift.Update(kline.High.Add(kline.Low).Add(kline.Close).Div(Three).Float64()) source := getSource(&kline).Float64()
s.drift.Update(source)
s.atr.Update(kline.High.Float64(), kline.Low.Float64(), kline.Close.Float64()) s.atr.Update(kline.High.Float64(), kline.Low.Float64(), kline.Close.Float64())
} }
@ -198,17 +211,46 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
} }
}) })
dynamicKLine := &types.KLine{}
priceLine := types.NewQueue(100)
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
if s.Status != types.StrategyStatusRunning { if s.Status != types.StrategyStatusRunning {
return return
} }
if kline.Symbol != s.Symbol || kline.Interval != s.Interval { if kline.Symbol != s.Symbol {
return return
} }
hlc3 := kline.High.Add(kline.Low).Add(kline.Close).Div(Three) var driftPred, atr float64
s.drift.Update(hlc3.Float64()) var drift []float64
if !kline.Closed {
return
}
if kline.Interval == types.Interval1m {
return
}
dynamicKLine.Copy(&kline)
source := getSource(dynamicKLine)
sourcef := source.Float64()
priceLine.Update(sourcef)
dynamicKLine.Closed = false
s.drift.Update(sourcef)
drift = s.drift.Array(2)
driftPred = s.drift.Predict(3)
atr = s.atr.Last()
price := s.GetLastPrice() price := s.GetLastPrice()
if s.drift.Last() < 0 && s.drift.Index(1) > 0 { avg := s.Position.AverageCost.Float64()
shortCondition := (driftPred <= 0 && drift[0] <= 0) && (s.Position.IsClosed() || s.Position.IsDust(fixedpoint.Max(price, source)))
longCondition := (driftPred >= 0 && drift[0] >= 0) && (s.Position.IsClosed() || s.Position.IsDust(fixedpoint.Min(price, source)))
exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= price.Float64() || avg*(1.+s.stoploss) <= price.Float64()) &&
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Max(price, source)))
exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= price.Float64() || avg*(1.-s.stoploss) >= price.Float64()) &&
(!s.Position.IsClosed() && !s.Position.IsDust(fixedpoint.Min(price, source)))
if shortCondition {
if s.ActiveOrderBook.NumOfOrders() > 0 { if s.ActiveOrderBook.NumOfOrders() > 0 {
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil { if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
log.WithError(err).Errorf("cannot cancel orders") log.WithError(err).Errorf("cannot cancel orders")
@ -220,22 +262,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
log.Errorf("unable to get baseBalance") log.Errorf("unable to get baseBalance")
return return
} }
if hlc3.Compare(price) < 0 { if source.Compare(price) < 0 {
hlc3 = price source = price
} }
if s.Market.IsDustQuantity(baseBalance.Available, hlc3) { if s.Market.IsDustQuantity(baseBalance.Available, source) {
return
}
if !s.Position.IsClosed() && !s.Position.IsDust(hlc3) {
return return
} }
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{ _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol, Symbol: s.Symbol,
Side: types.SideTypeSell, Side: types.SideTypeSell,
Type: types.OrderTypeLimitMaker, Type: types.OrderTypeLimitMaker,
Price: hlc3, Price: source,
StopPrice: hlc3.Add(fixedpoint.NewFromFloat(s.atr.Last() / 3)), StopPrice: fixedpoint.NewFromFloat(sourcef + atr/2),
Quantity: baseBalance.Available, Quantity: baseBalance.Available,
}) })
if err != nil { if err != nil {
@ -243,15 +282,24 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
return return
} }
} }
if s.drift.Last() > 0 && s.drift.Index(1) < 0 { if exitShortCondition {
if s.ActiveOrderBook.NumOfOrders() > 0 { if s.ActiveOrderBook.NumOfOrders() > 0 {
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil { if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
log.WithError(err).Errorf("cannot cancel orders") log.WithError(err).Errorf("cannot cancel orders")
return return
} }
} }
if hlc3.Compare(price) > 0 { _, _ = s.ClosePosition(ctx)
hlc3 = price }
if longCondition {
if s.ActiveOrderBook.NumOfOrders() > 0 {
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
return
}
}
if source.Compare(price) > 0 {
source = price
} }
quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency) quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency)
if !ok { if !ok {
@ -259,29 +307,50 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
return return
} }
if s.Market.IsDustQuantity( if s.Market.IsDustQuantity(
quoteBalance.Available.Div(hlc3), hlc3) { quoteBalance.Available.Div(source), source) {
return return
} }
if !s.Position.IsClosed() && !s.Position.IsDust(hlc3) { if !s.Position.IsClosed() && !s.Position.IsDust(source) {
return return
} }
_, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{ _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: s.Symbol, Symbol: s.Symbol,
Side: types.SideTypeBuy, Side: types.SideTypeBuy,
Type: types.OrderTypeLimitMaker, Type: types.OrderTypeLimitMaker,
Price: hlc3, Price: source,
StopPrice: hlc3.Sub(fixedpoint.NewFromFloat(s.atr.Last() / 3)), StopPrice: fixedpoint.NewFromFloat(sourcef - atr/2),
Quantity: quoteBalance.Available.Div(hlc3), Quantity: quoteBalance.Available.Div(source),
}) })
if err != nil { if err != nil {
log.WithError(err).Errorf("cannot place buy order") log.WithError(err).Errorf("cannot place buy order")
return return
} }
} }
if exitLongCondition {
if s.ActiveOrderBook.NumOfOrders() > 0 {
if err := s.GeneralOrderExecutor.GracefulCancelActiveOrderBook(ctx, s.ActiveOrderBook); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
return
}
}
_, _ = s.ClosePosition(ctx)
}
}) })
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) { bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String()) _, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
canvas := types.NewCanvas(s.InstanceID(), s.Interval)
fmt.Println(dynamicKLine.StartTime, dynamicKLine.EndTime)
mean := priceLine.Mean(100)
highestPrice := priceLine.Highest(100)
highestDrift := s.drift.Highest(100)
ratio := highestDrift / highestPrice
canvas.Plot("drift", s.drift, dynamicKLine.StartTime, 100)
canvas.Plot("zero", types.NumberSeries(0), dynamicKLine.StartTime, 100)
canvas.Plot("price", priceLine.Minus(mean).Mul(ratio), dynamicKLine.StartTime, 100)
f, _ := os.Create("output.png")
defer f.Close()
canvas.Render(chart.PNG, f)
wg.Done() wg.Done()
}) })
return nil return nil

View File

@ -12,6 +12,10 @@ func (s *Float64Slice) Push(v float64) {
*s = append(*s, v) *s = append(*s, v)
} }
func (s *Float64Slice) Update(v float64) {
*s = append(*s, v)
}
func (s *Float64Slice) Pop(i int64) (v float64) { func (s *Float64Slice) Pop(i int64) (v float64) {
v = (*s)[i] v = (*s)[i]
*s = append((*s)[:i], (*s)[i+1:]...) *s = append((*s)[:i], (*s)[i+1:]...)

View File

@ -3,9 +3,11 @@ package types
import ( import (
"fmt" "fmt"
"math" "math"
"time"
"reflect" "reflect"
"gonum.org/v1/gonum/stat" "gonum.org/v1/gonum/stat"
"github.com/wcharczuk/go-chart/v2"
) )
// Super basic Series type that simply holds the float64 data // Super basic Series type that simply holds the float64 data
@ -43,6 +45,15 @@ func (inc *Queue) Length() int {
return len(inc.arr) return len(inc.arr)
} }
func (inc *Queue) Clone() *Queue {
out := &Queue {
arr: inc.arr[:],
size: inc.size,
}
out.SeriesBase.Series = out
return out
}
func (inc *Queue) Update(v float64) { func (inc *Queue) Update(v float64) {
inc.arr = append(inc.arr, v) inc.arr = append(inc.arr, v)
if len(inc.arr) > inc.size { if len(inc.arr) > inc.size {
@ -50,7 +61,7 @@ func (inc *Queue) Update(v float64) {
} }
} }
var _ SeriesExtend = &Queue{} var _ UpdatableSeriesExtend = &Queue{}
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data. // Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
type Float64Indicator interface { type Float64Indicator interface {
@ -93,6 +104,7 @@ type SeriesExtend interface {
Variance(length int) float64 Variance(length int) float64
Covariance(b Series, length int) float64 Covariance(b Series, length int) float64
Correlation(b Series, length int, method ...CorrFunc) float64 Correlation(b Series, length int, method ...CorrFunc) float64
AutoCorrelation(length int, lag ...int) float64
Rank(length int) SeriesExtend Rank(length int) SeriesExtend
Sigmoid() SeriesExtend Sigmoid() SeriesExtend
Softmax(window int) SeriesExtend Softmax(window int) SeriesExtend
@ -120,6 +132,24 @@ type UpdatableSeriesExtend interface {
Update(float64) Update(float64)
} }
func Clone(u UpdatableSeriesExtend) UpdatableSeriesExtend {
method, ok := reflect.TypeOf(u).MethodByName("Clone")
if ok {
out := method.Func.Call([]reflect.Value{reflect.ValueOf(u)})
return out[0].Interface().(UpdatableSeriesExtend)
}
panic("method Clone not exist")
}
func TestUpdate(u UpdatableSeriesExtend, input float64) UpdatableSeriesExtend {
method, ok := reflect.TypeOf(u).MethodByName("TestUpdate")
if ok {
out := method.Func.Call([]reflect.Value{reflect.ValueOf(u), reflect.ValueOf(input)})
return out[0].Interface().(UpdatableSeriesExtend)
}
panic("method TestUpdate not exist")
}
// The interface maps to pinescript basic type `series` for bool type // The interface maps to pinescript basic type `series` for bool type
// Access the internal historical data from the latest to the oldest // Access the internal historical data from the latest to the oldest
// Index(0) always maps to Last() // Index(0) always maps to Last()
@ -335,6 +365,10 @@ func (a NumberSeries) Length() int {
return math.MaxInt32 return math.MaxInt32
} }
func (a NumberSeries) Clone() NumberSeries {
return a
}
var _ Series = NumberSeries(0) var _ Series = NumberSeries(0)
type AddSeriesResult struct { type AddSeriesResult struct {
@ -601,7 +635,7 @@ func Array(a Series, limit ...int) (result []float64) {
if len(limit) > 0 { if len(limit) > 0 {
l = limit[0] l = limit[0]
} }
if l < a.Length() { if l > a.Length() {
l = a.Length() l = a.Length()
} }
result = make([]float64, l) result = make([]float64, l)
@ -621,7 +655,7 @@ func Reverse(a Series, limit ...int) (result Float64Slice) {
if len(limit) > 0 { if len(limit) > 0 {
l = limit[0] l = limit[0]
} }
if l < a.Length() { if l > a.Length() {
l = a.Length() l = a.Length()
} }
result = make([]float64, l) result = make([]float64, l)
@ -817,6 +851,17 @@ func Correlation(a Series, b Series, length int, method ...CorrFunc) float64 {
return runner(a, b, length) return runner(a, b, length)
} }
// similar to pandas.Series.autocorr() function.
//
// The method computes the Pearson correlation between Series and shifted itself
func AutoCorrelation(a Series, length int, lags ...int) float64 {
lag := 1
if len(lags) > 0 {
lag = lags[0]
}
return Pearson(a, Shift(a, lag), length)
}
// similar to pandas.Series.cov() function with ddof=0 // similar to pandas.Series.cov() function with ddof=0
// //
// Compute covariance with Series // Compute covariance with Series
@ -1118,4 +1163,47 @@ func (l *LogisticRegressionModel) Predict(x []float64) float64 {
return sigmoid(z + l.Gradient) return sigmoid(z + l.Gradient)
} }
type Canvas struct {
chart.Chart
Interval Interval
}
func NewCanvas(title string, interval Interval) *Canvas {
valueFormatter := chart.TimeValueFormatter
if interval.Minutes() > 24 * 60 {
valueFormatter = chart.TimeDateValueFormatter
} else if interval.Minutes() > 60 {
valueFormatter = chart.TimeHourValueFormatter
} else {
valueFormatter = chart.TimeMinuteValueFormatter
}
out := &Canvas {
Chart: chart.Chart {
Title: title,
XAxis: chart.XAxis{
ValueFormatter: valueFormatter,
},
},
Interval: interval,
}
out.Chart.Elements = []chart.Renderable{
chart.LegendLeft(&out.Chart),
}
return out
}
func (canvas *Canvas) Plot(tag string, a Series, endTime Time, length int) {
var timeline []time.Time
e := endTime.Time()
for i := length - 1; i >= 0; i-- {
shiftedT := e.Add(-time.Duration(i * canvas.Interval.Minutes()) * time.Minute)
timeline = append(timeline, shiftedT)
}
canvas.Series = append(canvas.Series, chart.TimeSeries{
Name: tag,
YValues: Reverse(a, length),
XValues: timeline,
})
}
// TODO: ta.linreg // TODO: ta.linreg

View File

@ -1,9 +1,13 @@
package types package types
import ( import (
//"os"
"testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gonum.org/v1/gonum/stat" "gonum.org/v1/gonum/stat"
"testing" "github.com/wcharczuk/go-chart/v2"
) )
func TestFloat(t *testing.T) { func TestFloat(t *testing.T) {
@ -144,3 +148,23 @@ func TestDot(t *testing.T) {
out3 := Dot(3., &a, 2) out3 := Dot(3., &a, 2)
assert.InDelta(t, out2, out3, 0.001) assert.InDelta(t, out2, out3, 0.001)
} }
func TestClone(t *testing.T) {
a := NewQueue(3)
a.Update(3.)
b := Clone(a)
b.Update(4.)
assert.Equal(t, a.Last(), 3.)
assert.Equal(t, b.Last(), 4.)
}
func TestPlot(t *testing.T) {
ct := NewCanvas("test", Interval5m)
a := Float64Slice{200., 205., 230., 236}
ct.Plot("test", &a, Time(time.Now()), 4)
assert.Equal(t, ct.Interval, Interval5m)
assert.Equal(t, ct.Series[0].(chart.TimeSeries).Len(), 4)
//f, _ := os.Create("output.png")
//defer f.Close()
//ct.Render(chart.PNG, f)
}

View File

@ -71,6 +71,26 @@ type KLine struct {
Closed bool `json:"closed" db:"closed"` Closed bool `json:"closed" db:"closed"`
} }
func (k *KLine) Copy(o *KLine) {
k.GID = o.GID
k.Exchange = o.Exchange
k.Symbol = o.Symbol
k.StartTime = o.StartTime
k.EndTime = o.EndTime
k.Interval = o.Interval
k.Open = o.Open
k.Close = o.Close
k.High = o.High
k.Low = o.Low
k.Volume = o.Volume
k.QuoteVolume = o.QuoteVolume
k.TakerBuyBaseAssetVolume = o.TakerBuyBaseAssetVolume
k.TakerBuyQuoteAssetVolume = o.TakerBuyQuoteAssetVolume
k.LastTradeID = o.LastTradeID
k.NumberOfTrades = o.NumberOfTrades
k.Closed = o.Closed
}
func (k KLine) GetStartTime() Time { func (k KLine) GetStartTime() Time {
return k.StartTime return k.StartTime
} }

View File

@ -121,6 +121,10 @@ func (s *SeriesBase) Correlation(b Series, length int, method ...CorrFunc) float
return Correlation(s, b, length, method...) return Correlation(s, b, length, method...)
} }
func (s *SeriesBase) AutoCorrelation(length int, lag ...int) float64 {
return AutoCorrelation(s, length, lag...)
}
func (s *SeriesBase) Rank(length int) SeriesExtend { func (s *SeriesBase) Rank(length int) SeriesExtend {
return Rank(s, length) return Rank(s, length)
} }