feature: export drift1m, remove take profit, add profit report for listing pnl by date

This commit is contained in:
zenix 2022-08-15 20:50:34 +09:00
parent da28750313
commit c1d9df8cdb
8 changed files with 135 additions and 112 deletions

View File

@ -31,25 +31,25 @@ exchangeStrategies:
stoploss: 0.3%
source: close
predictOffset: 2
# the init value of takeProfitFactor Series, position avg +- takeProfitFactor * atr as take profit price
takeProfitFactor: 6
profitFactorWindow: 8
noTrailingStopLoss: false
trailingStopLossType: kline
# stddev on high/low-source
hlVarianceMultiplier: 0.23
hlRangeWindow: 5
window1m: 24
smootherWindow1m: 24
fisherTransformWindow1m: 162
smootherWindow: 1
fisherTransformWindow: 9
atrWindow: 14
# orders not been traded will be canceled after `pendingMinutes` minutes
pendingMinutes: 4
pendingMinutes: 3
noRebalance: true
trendWindow: 12
rebalanceFilter: 1.5
trailingActivationRatio: [0.008, 0.015]
trailingCallbackRate: [0.002, 0.001]
trailingActivationRatio: [0.004]
trailingCallbackRate: [0.001]
generateGraph: true
graphPNLDeductFee: true
@ -88,7 +88,7 @@ sync:
backtest:
startTime: "2022-01-01"
endTime: "2022-07-30"
endTime: "2022-08-30"
symbols:
- ETHBUSD
sessions: [binance]

View File

@ -38,6 +38,9 @@ exchangeStrategies:
hlRangeWindow: 5
smootherWindow: 1
fisherTransformWindow: 9
window1m: 28
smootherWindow1m: 14
fisherTransformWindow1m: 162
# the init value of takeProfitFactor Series, the coefficient of ATR as TP
takeProfitFactor: 4
profitFactorWindow: 5
@ -51,11 +54,11 @@ exchangeStrategies:
# ActivationRatio should be increasing order
# when farest price from entry goes over that ratio, start using the callback ratio accordingly to do trailingstop
#trailingActivationRatio: [0.007, 0.011, 0.02, 0.05]
trailingActivationRatio: [0.001, 0.002, 0.004]
trailingActivationRatio: [0.002, 0.02, 0.04]
#trailingActivationRatio: []
#trailingCallbackRate: []
#trailingCallbackRate: [0.002, 0.001, 0.002, 0.001]
trailingCallbackRate: [0.0005, 0.0008, 0.002]
trailingCallbackRate: [0.0005, 0.001, 0.003]
generateGraph: true
graphPNLDeductFee: false
@ -128,5 +131,5 @@ backtest:
makerFeeRate: 0.000
#takerFeeRate: 0.000
balances:
BTC: 1
USDT: 5000
BTC: 0
USDT: 21

2
go.mod
View File

@ -25,6 +25,7 @@ require (
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/jedib0t/go-pretty/v6 v6.3.6
github.com/jmoiron/sqlx v1.3.4
github.com/joho/godotenv v1.3.0
github.com/leekchan/accounting v0.0.0-20191218023648-17a4ce5f94d4
@ -83,7 +84,6 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jedib0t/go-pretty/v6 v6.3.6 // indirect
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect

11
go.sum
View File

@ -81,7 +81,6 @@ github.com/c9s/requestgen v1.3.0/go.mod h1:5n9FU3hr5307IiXAmbMiZbHYaPiys1u9jCWYe
github.com/c9s/rockhopper v1.2.2-0.20220617053729-ffdc87df194b h1:wT8c03PHLv7+nZUIGqxAzRvIfYHNxMCNVWwvdGkOXTs=
github.com/c9s/rockhopper v1.2.2-0.20220617053729-ffdc87df194b/go.mod h1:EKObf66Cp7erWxym2de+07qNN5T1N9PXxHdh97N44EQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@ -149,7 +148,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@ -441,7 +439,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@ -603,6 +600,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -611,7 +609,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
@ -622,7 +619,6 @@ github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.3 h1:WbFSXLxDFKVN69Sk8t+XHGzVCD7R8UoAATR8NqZgTbk=
github.com/ugorji/go v1.2.3/go.mod h1:5l8GZ8hZvmL4uMdy+mhCO1LjswGRYco9Q3HfuisB21A=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.3 h1:/mVYEV+Jo3IZKeA5gBngN0AvNnQltEDkR+eQikkWQu0=
@ -642,7 +638,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
@ -775,8 +770,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@ -855,7 +848,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -928,7 +920,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -34,6 +34,10 @@ func (inc *FisherTransform) Update(value float64) {
inc.prices.Update(value)
highest := inc.prices.Highest(inc.Window)
lowest := inc.prices.Lowest(inc.Window)
if highest == lowest {
inc.Values.Update(0)
return
}
x := 2*((value-lowest)/(highest-lowest)) - 1
if x == 1 {
x = 0.9999

View File

@ -43,6 +43,7 @@ func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
}
fieldName := t.Name
typeName := field.Type().String()
log.Infof("fieldName %s typeName %s", fieldName, typeName)
value := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem()
isSeries := true
lastFunc := value.MethodByName("Last")
@ -82,7 +83,7 @@ func (s *Strategy) ParamDump(f io.Writer, seriesLength ...int) {
fmt.Fprintf(f, "%s: %v", fieldName, field.Interface())
} else if field.Type().Kind() == reflect.Map {
fmt.Fprintf(f, "%s: {", fieldName)
iter := field.MapRange()
iter := value.MapRange()
for iter.Next() {
k := iter.Key().Interface()
v := iter.Value().Interface()
@ -203,10 +204,7 @@ func (s *Strategy) Print(f io.Writer, pretty bool, withColor ...bool) {
}
}
if pretty {
rows = append(rows, table.Row{"takeProfitFactor(last)", "takeProfitFactor", "float64", s.takeProfitFactor.Last()})
t.AppendRows(rows)
t.Render()
} else {
hiyellow(f, "takeProfitFactor(last): %f\n", s.takeProfitFactor.Last())
}
}

View File

@ -73,12 +73,7 @@ type Strategy struct {
beta float64
// This stores the maximum TP coefficient of ATR multiplier of each entry point
takeProfitFactor types.UpdatableSeriesExtend
Source string `json:"source,omitempty"`
TakeProfitFactor float64 `json:"takeProfitFactor"`
ProfitFactorWindow int `json:"profitFactorWindow"`
StopLoss fixedpoint.Value `json:"stoploss"`
CanvasPath string `json:"canvasPath"`
PredictOffset int `json:"predictOffset"`
@ -86,6 +81,9 @@ type Strategy struct {
NoTrailingStopLoss bool `json:"noTrailingStopLoss"`
TrailingStopLossType string `json:"trailingStopLossType"` // trailing stop sources. Possible options are `kline` for 1m kline and `realtime` from order updates
HLRangeWindow int `json:"hlRangeWindow"`
Window1m int `json:"window1m"`
SmootherWindow1m int `json:"smootherWindow1m"`
FisherTransformWindow1m int `json:"fisherTransformWindow1m"`
SmootherWindow int `json:"smootherWindow"`
FisherTransformWindow int `json:"fisherTransformWindow"`
ATRWindow int `json:"atrWindow"`
@ -225,24 +223,20 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
s.drift.SeriesBase.Series = s.drift
s.drift1m = &DriftMA{
drift: &indicator.Drift{
MA: &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: 2}},
IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: 2},
MA: &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m}},
IntervalWindow: types.IntervalWindow{Interval: types.Interval1m, Window: s.Window1m},
},
ma1: &indicator.EWMA{
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 24},
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SmootherWindow1m},
},
ma2: &indicator.FisherTransform{
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FisherTransformWindow * 15},
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FisherTransformWindow1m},
},
}
s.drift1m.SeriesBase.Series = s.drift1m
s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.ATRWindow}}
s.takeProfitFactor = &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.ProfitFactorWindow}}
s.trendLine = &indicator.EWMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.TrendWindow}}
for i := 0; i <= s.ProfitFactorWindow; i++ {
s.takeProfitFactor.Update(s.TakeProfitFactor)
}
store, _ := s.Session.MarketDataStore(s.Symbol)
klines, ok := store.KLinesOfInterval(s.Interval)
if !ok {
@ -271,6 +265,9 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
for _, kline := range *klines {
source := s.getSource(&kline).Float64()
s.drift1m.Update(source)
if s.drift1m.Last() != s.drift1m.Last() {
panic(fmt.Sprintf("%f %v %f %f", source, s.drift1m.drift.Values.Index(1), s.drift1m.ma2.Last(), s.drift1m.drift.LastValue))
}
}
if s.kline1m != nil && klines != nil {
s.kline1m.Set(&(*klines)[len(*klines)-1])
@ -278,7 +275,7 @@ func (s *Strategy) initIndicators(priceLines *types.Queue) error {
return nil
}
func (s *Strategy) smartCancel(ctx context.Context, pricef, atr, takeProfitFactor float64) (int, error) {
func (s *Strategy) smartCancel(ctx context.Context, pricef, atr float64) (int, error) {
nonTraded := s.GeneralOrderExecutor.ActiveMakerOrders().Orders()
if len(nonTraded) > 0 {
if len(nonTraded) > 1 {
@ -364,7 +361,9 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
bestAsk := ticker.Sell
var pricef, atr float64
if util.TryLock(&s.lock) {
if !util.TryLock(&s.lock) {
return
}
if !bestAsk.IsZero() && !bestBid.IsZero() {
s.midPrice = bestAsk.Add(bestBid).Div(Two)
} else if !bestAsk.IsZero() {
@ -373,30 +372,12 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
s.midPrice = bestBid
}
pricef = s.midPrice.Float64()
} else {
return
}
s.lock.Unlock()
// for trailing stoploss during the realtime
if s.NoTrailingStopLoss || s.TrailingStopLossType == "kline" {
return
}
stoploss := s.StopLoss.Float64()
atr = s.atr.Last()
takeProfitFactor := s.takeProfitFactor.Predict(2)
numPending := 0
var err error
if numPending, err = s.smartCancel(ctx, pricef, atr, takeProfitFactor); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
if !util.TryLock(&s.positionLock) {
return
}
if numPending > 0 {
return
}
s.positionLock.Lock()
if s.highestPrice > 0 && s.highestPrice < pricef {
s.highestPrice = pricef
@ -404,18 +385,29 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
if s.lowestPrice > 0 && s.lowestPrice > pricef {
s.lowestPrice = pricef
}
// for trailing stoploss during the realtime
if s.NoTrailingStopLoss || s.TrailingStopLossType == "kline" {
s.positionLock.Unlock()
return
}
exitShortCondition := s.sellPrice > 0 && (s.sellPrice*(1.+stoploss) <= pricef || s.sellPrice-atr*takeProfitFactor >= pricef ||
stoploss := s.StopLoss.Float64()
atr = s.atr.Last()
numPending := 0
var err error
if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
return
}
if numPending > 0 {
return
}
exitShortCondition := s.sellPrice > 0 && (s.sellPrice*(1.+stoploss) <= pricef ||
s.trailingCheck(pricef, "short"))
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef || s.buyPrice+atr*takeProfitFactor <= pricef ||
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= pricef ||
s.trailingCheck(pricef, "long"))
if exitShortCondition || exitLongCondition {
if exitLongCondition && s.highestPrice > s.buyPrice {
s.takeProfitFactor.Update((s.highestPrice - s.buyPrice) / atr * 1.5)
}
if exitShortCondition && s.sellPrice > s.lowestPrice {
s.takeProfitFactor.Update((s.sellPrice - s.lowestPrice) / atr * 1.5)
}
log.Infof("Close position by orderbook changes")
s.positionLock.Unlock()
_ = s.ClosePosition(ctx, fixedpoint.One)
@ -453,12 +445,20 @@ func (s *Strategy) DrawIndicators(time types.Time, priceLine types.SeriesExtend,
highestPrice := priceLine.Minus(mean).Abs().Highest(Length)
highestDrift := s.drift.Abs().Highest(Length)
hi := s.drift.drift.Abs().Highest(Length)
h1m := s.drift1m.Abs().Highest(Length * s.Interval.Minutes())
ratio := highestPrice / highestDrift
for i := 0; i < s.drift1m.Length(); i++ {
if s.drift1m.Index(i) != s.drift1m.Index(i) {
log.Infof("%d, %f", i, s.drift1m.Index(i-1))
}
}
log.Infof("%d, %v", s.drift1m.Length(), s.drift1m.Array(10))
canvas.Plot("upband", s.ma.Add(s.stdevHigh), time, Length)
canvas.Plot("ma", s.ma, time, Length)
canvas.Plot("downband", s.ma.Minus(s.stdevLow), time, Length)
canvas.Plot("drift", s.drift.Mul(ratio).Add(mean), time, Length)
canvas.Plot("driftOrig", s.drift.drift.Mul(highestPrice/hi).Add(mean), time, Length)
canvas.Plot("drift1m", s.drift1m.Mul(highestPrice/h1m).Add(mean), time, Length*s.Interval.Minutes(), types.Interval1m)
canvas.Plot("zero", types.NumberSeries(mean), time, Length)
canvas.Plot("price", priceLine, time, Length)
canvas.Plot("zeroPoint", zeroPoints, time, Length)
@ -821,10 +821,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
price := s.getLastPrice()
pricef := price.Float64()
takeProfitFactor := s.takeProfitFactor.Predict(2)
var err error
numPending := 0
if numPending, err = s.smartCancel(ctx, pricef, atr, takeProfitFactor); err != nil {
if numPending, err = s.smartCancel(ctx, pricef, atr); err != nil {
log.WithError(err).Errorf("cannot cancel orders")
return
}
@ -842,17 +841,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if s.highestPrice > 0 && highf > s.highestPrice {
s.highestPrice = highf
}
exitShortCondition := s.sellPrice > 0 && (s.sellPrice*(1.+stoploss) <= highf || s.sellPrice-atr*takeProfitFactor >= lowf ||
s.trailingCheck(highf, "short"))
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf || s.buyPrice+atr*takeProfitFactor <= highf ||
s.trailingCheck(lowf, "long"))
exitShortCondition := s.sellPrice > 0 && (s.sellPrice*(1.+stoploss) <= highf ||
s.trailingCheck(highf, "short") || s.drift1m.Last() > 0)
exitLongCondition := s.buyPrice > 0 && (s.buyPrice*(1.-stoploss) >= lowf ||
s.trailingCheck(lowf, "long") || s.drift1m.Last() < 0)
if exitShortCondition || exitLongCondition {
if exitLongCondition && s.highestPrice > s.buyPrice {
s.takeProfitFactor.Update((s.highestPrice - s.buyPrice) / atr * 1.5)
}
if exitShortCondition && s.sellPrice > s.lowestPrice {
s.takeProfitFactor.Update((s.sellPrice - s.lowestPrice) / atr * 1.5)
}
s.positionLock.Unlock()
_ = s.ClosePosition(ctx, fixedpoint.One)
} else {
@ -901,15 +894,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
if s.highestPrice > 0 && highf > s.highestPrice {
s.highestPrice = highf
}
takeProfitFactor := s.takeProfitFactor.Predict(2)
if !s.NoRebalance {
s.Rebalance(ctx)
}
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, ddriftPred: %.4f, drift[1]: %.4f, ddrift[1]: %.4f, atr: %.4f, takeProfitFact: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f",
sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, takeProfitFactor, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, ddriftPred: %.4f, drift[1]: %.4f, ddrift[1]: %.4f, atr: %.4f, lowf %.4f, highf: %.4f lowest: %.4f highest: %.4f sp %.4f bp %.4f",
sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, lowf, highf, s.lowestPrice, s.highestPrice, s.sellPrice, s.buyPrice)
// Notify will parse args to strings and process separately
bbgo.Notify("balances: [Base] %s(%vU) [Quote] %s",
balances[s.Market.BaseCurrency].String(),
@ -927,12 +919,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
}
exitShortCondition := s.sellPrice > 0 && ( //!shortCondition && !longCondition ||
s.sellPrice*(1.+stoploss) <= highf ||
s.trailingCheck(pricef, "short") ||
s.sellPrice-atr*takeProfitFactor >= s.lowestPrice)
s.trailingCheck(pricef, "short"))
exitLongCondition := s.buyPrice > 0 && ( //!longCondition && !shortCondition ||
s.buyPrice*(1.-stoploss) >= lowf ||
s.trailingCheck(pricef, "long") ||
s.buyPrice+atr*takeProfitFactor <= s.highestPrice)
s.trailingCheck(pricef, "long"))
if exitShortCondition || exitLongCondition {
if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil {
@ -940,15 +930,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.positionLock.Unlock()
return
}
if exitShortCondition && s.sellPrice > s.lowestPrice {
s.takeProfitFactor.Update((s.sellPrice - s.lowestPrice) / atr * 1.5)
}
if exitLongCondition && s.buyPrice < s.highestPrice {
s.takeProfitFactor.Update((s.highestPrice - s.buyPrice) / atr * 1.5)
}
if s.takeProfitFactor.Last() == 0 {
log.Errorf("exit %f %f %v", s.highestPrice, s.lowestPrice, s.takeProfitFactor.Array(10))
}
s.positionLock.Unlock()
_ = s.ClosePosition(ctx, fixedpoint.One)
return
@ -976,9 +957,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.positionLock.Unlock()
return
}
if s.sellPrice > s.lowestPrice && s.lowestPrice > 0 {
s.takeProfitFactor.Update((s.sellPrice - s.lowestPrice) / atr * 1.5)
}
s.positionLock.Unlock()
quantity := quoteBalance.Available.Div(source)
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
@ -1018,9 +996,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.positionLock.Unlock()
return
}
if s.buyPrice < s.highestPrice && s.buyPrice > 0 {
s.takeProfitFactor.Update((s.highestPrice - s.buyPrice) / atr * 1.5)
}
s.positionLock.Unlock()
// Cleanup pending StopOrders
quantity := baseBalance.Available
@ -1039,6 +1014,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
s.orderPendingCounter[createdOrders[0].OrderID] = s.minutesCounter
return
}
s.positionLock.Unlock()
})
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
@ -1046,6 +1022,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
var buffer bytes.Buffer
s.Print(&buffer, true, true)
fmt.Fprintln(&buffer, "--- NonProfitable Dates ---")
for _, daypnl := range s.TradeStats.IntervalProfits[types.Interval1d].GetNonProfitableIntervals() {
fmt.Fprintf(&buffer, "%s\n", daypnl)
}
fmt.Fprintln(&buffer, s.TradeStats.BriefString())
os.Stdout.Write(buffer.Bytes())

View File

@ -1,6 +1,8 @@
package types
import (
"encoding/json"
"log"
"math"
"time"
@ -12,11 +14,12 @@ import (
type IntervalProfitCollector struct {
Interval Interval `json:"interval"`
Profits *Float64Slice `json:"profits"`
Timestamp *Float64Slice `json:"timestamp"`
tmpTime time.Time `json:"tmpTime"`
}
func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector {
return &IntervalProfitCollector{Interval: i, tmpTime: startTime, Profits: &Float64Slice{1.}}
return &IntervalProfitCollector{Interval: i, tmpTime: startTime, Profits: &Float64Slice{1.}, Timestamp: &Float64Slice{float64(startTime.Unix())}}
}
// Update the collector by every traded profit
@ -31,6 +34,7 @@ func (s *IntervalProfitCollector) Update(profit *Profit) {
for {
s.Profits.Update(1.)
s.tmpTime = s.tmpTime.Add(duration)
s.Timestamp.Update(float64(s.tmpTime.Unix()))
if profit.TradedAt.Before(s.tmpTime.Add(duration)) {
(*s.Profits)[len(*s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64()
break
@ -40,6 +44,48 @@ func (s *IntervalProfitCollector) Update(profit *Profit) {
}
}
type ProfitReport struct {
StartTime time.Time `json:"startTime"`
Profit float64 `json:"profit"`
Interval Interval `json:"interval"`
}
func (s ProfitReport) String() string {
b, err := json.MarshalIndent(s, "", "\t")
if err != nil {
log.Fatal(err)
}
return string(b)
}
// Get all none-profitable intervals
func (s *IntervalProfitCollector) GetNonProfitableIntervals() (result []ProfitReport) {
if s.Profits == nil {
return result
}
l := s.Profits.Length()
for i := 0; i < l; i++ {
if s.Profits.Index(i) <= 1. {
result = append(result, ProfitReport{StartTime: time.Unix(int64(s.Timestamp.Index(i)), 0), Profit: s.Profits.Index(i), Interval: s.Interval})
}
}
return result
}
// Get all profitable intervals
func (s *IntervalProfitCollector) GetProfitableIntervals() (result []ProfitReport) {
if s.Profits == nil {
return result
}
l := s.Profits.Length()
for i := 0; i < l; i++ {
if s.Profits.Index(i) > 1. {
result = append(result, ProfitReport{StartTime: time.Unix(int64(s.Timestamp.Index(i)), 0), Profit: s.Profits.Index(i), Interval: s.Interval})
}
}
return result
}
// Get number of profitable traded intervals
func (s *IntervalProfitCollector) GetNumOfProfitableIntervals() (profit int) {
if s.Profits == nil {