mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
fix: drift pnl calculation and graph drawing, filter periods other than s.Interval and 1m
This commit is contained in:
parent
4045ad54f9
commit
0e3aecb549
9
go.sum
9
go.sum
|
@ -352,6 +352,8 @@ github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0f
|
|||
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jedib0t/go-pretty/v6 v6.3.6 h1:A6w2BuyPMtf7M82BGRBys9bAba2C26ZX9lrlrZ7uH6U=
|
||||
github.com/jedib0t/go-pretty/v6 v6.3.6/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
|
||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
|
||||
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
|
@ -442,6 +444,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
|||
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=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
|
@ -503,6 +507,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -599,6 +604,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
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=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
@ -608,6 +614,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
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=
|
||||
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto=
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package drift
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -23,14 +23,16 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/interact"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
)
|
||||
|
||||
const ID = "drift"
|
||||
|
||||
const DDriftFilterNeg = -0.7
|
||||
const DDriftFilterPos = 0.7
|
||||
const DriftFilterNeg = -1.8
|
||||
const DriftFilterPos = 1.8
|
||||
const DriftFilterNeg = -1.85
|
||||
const DriftFilterPos = 1.85
|
||||
|
||||
var log = logrus.WithField("strategy", ID)
|
||||
var Four fixedpoint.Value = fixedpoint.NewFromInt(4)
|
||||
|
@ -64,7 +66,7 @@ type Strategy struct {
|
|||
stdevHigh *indicator.StdDev
|
||||
stdevLow *indicator.StdDev
|
||||
drift *DriftMA
|
||||
drift1m *indicator.Drift
|
||||
drift1m *DriftMA
|
||||
atr *indicator.ATR
|
||||
midPrice fixedpoint.Value
|
||||
lock sync.RWMutex
|
||||
|
@ -94,7 +96,7 @@ type Strategy struct {
|
|||
TrendWindow int `json:"trendWindow"` // trendLine is used for rebalancing the position. When trendLine goes up, hold base, otherwise hold quote
|
||||
RebalanceFilter float64 `json:"rebalanceFilter"` // beta filter on the Linear Regression of trendLine
|
||||
TrailingCallbackRate []float64 `json:"trailingCallbackRate"`
|
||||
TrailingActivationRatio []float64 `josn:"trailingActivationRatio"`
|
||||
TrailingActivationRatio []float64 `json:"trailingActivationRatio"`
|
||||
|
||||
buyPrice float64 `persistence:"buy_price"`
|
||||
sellPrice float64 `persistence:"sell_price"`
|
||||
|
@ -117,47 +119,122 @@ type Strategy struct {
|
|||
getSource SourceFunc
|
||||
}
|
||||
|
||||
func (s *Strategy) Print(o io.Writer, withColor ...bool) {
|
||||
f := bufio.NewWriter(o)
|
||||
defer f.Flush()
|
||||
b, _ := json.MarshalIndent(s.ExitMethods, " ", " ")
|
||||
type jsonStruct struct {
|
||||
key string
|
||||
json string
|
||||
tp string
|
||||
value interface{}
|
||||
}
|
||||
type jsonArr []jsonStruct
|
||||
|
||||
func (a jsonArr) Len() int { return len(a) }
|
||||
func (a jsonArr) Less(i, j int) bool { return a[i].key < a[j].key }
|
||||
func (a jsonArr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func (s *Strategy) Print(f io.Writer, pretty bool, withColor ...bool) {
|
||||
//b, _ := json.MarshalIndent(s.ExitMethods, " ", " ")
|
||||
|
||||
t := table.NewWriter()
|
||||
style := table.Style{
|
||||
Name: "StyleRounded",
|
||||
Box: table.StyleBoxRounded,
|
||||
Color: table.ColorOptionsDefault,
|
||||
Format: table.FormatOptionsDefault,
|
||||
HTML: table.DefaultHTMLOptions,
|
||||
Options: table.OptionsDefault,
|
||||
Title: table.TitleOptionsDefault,
|
||||
}
|
||||
var hiyellow func(io.Writer, string, ...interface{})
|
||||
if len(withColor) > 0 && withColor[0] {
|
||||
if pretty {
|
||||
style.Color = table.ColorOptionsYellowWhiteOnBlack
|
||||
style.Color.Row = text.Colors{text.FgHiYellow, text.BgHiBlack}
|
||||
style.Color.RowAlternate = text.Colors{text.FgYellow, text.BgBlack}
|
||||
}
|
||||
hiyellow = color.New(color.FgHiYellow).FprintfFunc()
|
||||
} else {
|
||||
hiyellow = func(a io.Writer, format string, args ...interface{}) {
|
||||
fmt.Fprintf(a, format, args...)
|
||||
}
|
||||
}
|
||||
if pretty {
|
||||
t.SetOutputMirror(f)
|
||||
t.SetStyle(style)
|
||||
t.AppendHeader(table.Row{"json", "struct field name", "type", "value"})
|
||||
}
|
||||
hiyellow(f, "------ %s Settings ------\n", s.InstanceID())
|
||||
hiyellow(f, "generateGraph: %v\n", s.GenerateGraph)
|
||||
hiyellow(f, "canvasPath: %s\n", s.CanvasPath)
|
||||
hiyellow(f, "graphPNLPath: %s\n", s.GraphPNLPath)
|
||||
hiyellow(f, "graphCumPNLPath: %s\n", s.GraphCumPNLPath)
|
||||
hiyellow(f, "source: %s\n", s.Source)
|
||||
hiyellow(f, "stoploss: %v\n", s.StopLoss)
|
||||
hiyellow(f, "takeProfitFactor(last): %f, (init): %f\n", s.takeProfitFactor.Last(), s.TakeProfitFactor)
|
||||
hiyellow(f, "profitFactorWindow: %d\n", s.ProfitFactorWindow)
|
||||
hiyellow(f, "predictOffset: %d\n", s.PredictOffset)
|
||||
hiyellow(f, "exits:\n %s\n", string(b))
|
||||
hiyellow(f, "symbol: %s\n", s.Symbol)
|
||||
hiyellow(f, "interval: %s\n", s.Interval)
|
||||
hiyellow(f, "window: %d\n", s.Window)
|
||||
hiyellow(f, "noTrailingStopLoss: %v\n", s.NoTrailingStopLoss)
|
||||
hiyellow(f, "trailingStopLossType: %s\n", s.TrailingStopLossType)
|
||||
hiyellow(f, "hlVarianceMutiplier: %f\n", s.HighLowVarianceMultiplier)
|
||||
hiyellow(f, "hlRangeWindow: %d\n", s.HLRangeWindow)
|
||||
hiyellow(f, "smootherWindow: %d\n", s.SmootherWindow)
|
||||
hiyellow(f, "fisherTransformWindow: %d\n", s.FisherTransformWindow)
|
||||
hiyellow(f, "atrWindow: %d\n", s.ATRWindow)
|
||||
hiyellow(f, "pendingMinutes: %d\n", s.PendingMinutes)
|
||||
hiyellow(f, "noRebalance: %v\n", s.NoRebalance)
|
||||
hiyellow(f, "\ttrendWindow: %d\n", s.TrendWindow)
|
||||
hiyellow(f, "\trebalanceFilter: %f\n", s.RebalanceFilter)
|
||||
hiyellow(f, "trailingActivationRatio: %v\n", s.TrailingActivationRatio)
|
||||
hiyellow(f, "trailingCallbackRate: %v\n", s.TrailingCallbackRate)
|
||||
hiyellow(f, "\n")
|
||||
|
||||
embeddedWhiteSet := map[string]struct{}{"Window": {}, "Interval": {}, "Symbol": {}}
|
||||
redundantSet := map[string]struct{}{}
|
||||
var rows []table.Row
|
||||
val := reflect.ValueOf(*s)
|
||||
var values jsonArr
|
||||
for i := 0; i < val.Type().NumField(); i++ {
|
||||
t := val.Type().Field(i)
|
||||
if !t.IsExported() {
|
||||
continue
|
||||
}
|
||||
fieldName := t.Name
|
||||
switch jsonTag := t.Tag.Get("json"); jsonTag {
|
||||
case "-":
|
||||
case "":
|
||||
if t.Anonymous {
|
||||
var target reflect.Type
|
||||
if t.Type.Kind() == reflect.Pointer {
|
||||
target = t.Type.Elem()
|
||||
} else {
|
||||
target = t.Type
|
||||
}
|
||||
for j := 0; j < target.NumField(); j++ {
|
||||
tt := target.Field(j)
|
||||
if !tt.IsExported() {
|
||||
continue
|
||||
}
|
||||
fieldName := tt.Name
|
||||
if _, ok := embeddedWhiteSet[fieldName]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if jtag := tt.Tag.Get("json"); jtag != "" && jtag != "-" {
|
||||
name := strings.Split(jtag, ",")[0]
|
||||
if _, ok := redundantSet[name]; ok {
|
||||
continue
|
||||
}
|
||||
redundantSet[name] = struct{}{}
|
||||
var value interface{}
|
||||
if t.Type.Kind() == reflect.Pointer {
|
||||
value = val.Field(i).Elem().Field(j).Interface()
|
||||
} else {
|
||||
value = val.Field(i).Field(j).Interface()
|
||||
}
|
||||
values = append(values, jsonStruct{key: fieldName, json: name, tp: tt.Type.String(), value: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
name := strings.Split(jsonTag, ",")[0]
|
||||
if _, ok := redundantSet[name]; ok {
|
||||
continue
|
||||
}
|
||||
redundantSet[name] = struct{}{}
|
||||
values = append(values, jsonStruct{key: fieldName, json: name, tp: t.Type.String(), value: val.Field(i).Interface()})
|
||||
}
|
||||
}
|
||||
sort.Sort(values)
|
||||
for _, value := range values {
|
||||
if pretty {
|
||||
rows = append(rows, table.Row{value.json, value.key, value.tp, value.value})
|
||||
} else {
|
||||
hiyellow(f, "%s: %v\n", value.json, value.value)
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
|
@ -165,7 +242,7 @@ func (s *Strategy) ID() string {
|
|||
}
|
||||
|
||||
func (s *Strategy) InstanceID() string {
|
||||
return fmt.Sprintf("%s:%s:%v", ID, s.Symbol, bbgo.IsBackTesting)
|
||||
return fmt.Sprintf("%s:%s:%v", ID, "" /*s.Symbol*/, bbgo.IsBackTesting)
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
|
@ -311,10 +388,19 @@ func (s *Strategy) initIndicators(kline *types.KLine, priceLines *types.Queue) e
|
|||
},
|
||||
}
|
||||
s.drift.SeriesBase.Series = s.drift
|
||||
s.drift1m = &indicator.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},
|
||||
},
|
||||
ma1: &indicator.EWMA{
|
||||
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 24},
|
||||
},
|
||||
ma2: &indicator.FisherTransform{
|
||||
IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FisherTransformWindow * 15},
|
||||
},
|
||||
}
|
||||
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}}
|
||||
|
@ -362,15 +448,23 @@ func (s *Strategy) smartCancel(ctx context.Context, pricef, atr, takeProfitFacto
|
|||
}
|
||||
toCancel := false
|
||||
|
||||
drift := s.drift1m.Array(2)
|
||||
for _, order := range nonTraded {
|
||||
if s.minutesCounter-s.orderPendingCounter[order.OrderID] > s.PendingMinutes {
|
||||
if order.Side == types.SideTypeBuy && drift[1] > drift[0] {
|
||||
continue
|
||||
} else if order.Side == types.SideTypeSell && drift[1] < drift[0] {
|
||||
continue
|
||||
}
|
||||
toCancel = true
|
||||
} else if order.Side == types.SideTypeBuy {
|
||||
if order.Price.Float64()+atr*takeProfitFactor <= pricef {
|
||||
// 75% of the probability
|
||||
if order.Price.Float64()+s.stdevHigh.Last()*2 <= pricef {
|
||||
toCancel = true
|
||||
}
|
||||
} else if order.Side == types.SideTypeSell {
|
||||
if order.Price.Float64()-atr*takeProfitFactor >= pricef {
|
||||
// 75% of the probability
|
||||
if order.Price.Float64()-s.stdevLow.Last()*2 >= pricef {
|
||||
toCancel = true
|
||||
}
|
||||
} else {
|
||||
|
@ -472,18 +566,19 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
|||
}
|
||||
avg = s.buyPrice + s.sellPrice
|
||||
|
||||
exitShortCondition := ( /*avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || (ddrift > 0 && drift > DDriftFilterPos) ||*/ avg-atr*takeProfitFactor >= pricef ||
|
||||
exitShortCondition := ( /*avg*(1.+stoploss) <= pricef || (ddrift > 0 && drift > DDriftFilterPos) ||*/ avg-atr*takeProfitFactor >= pricef ||
|
||||
s.trailingCheck(pricef, "short")) &&
|
||||
(s.p.IsShort() && !s.p.IsDust(price))
|
||||
exitLongCondition := ( /*avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || (ddrift < 0 && drift < DDriftFilterNeg) ||*/ avg+atr*takeProfitFactor <= pricef ||
|
||||
exitLongCondition := ( /*avg*(1.-stoploss) >= pricef || (ddrift < 0 && drift < DDriftFilterNeg) ||*/ avg+atr*takeProfitFactor <= pricef ||
|
||||
s.trailingCheck(pricef, "long")) &&
|
||||
(!s.p.IsLong() && !s.p.IsDust(price))
|
||||
if exitShortCondition || exitLongCondition {
|
||||
if exitLongCondition && s.highestPrice > avg {
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 4)
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 1.5)
|
||||
} else if exitShortCondition && avg > s.lowestPrice {
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 4)
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 1.5)
|
||||
}
|
||||
log.Infof("Close position by orderbook changes")
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
}
|
||||
})
|
||||
|
@ -494,6 +589,7 @@ func (s *Strategy) initTickerFunctions(ctx context.Context) {
|
|||
lastPrice, ok = s.Session.LastPrice(s.Symbol)
|
||||
if !ok {
|
||||
log.Error("cannot get lastprice")
|
||||
s.lock.RUnlock()
|
||||
return lastPrice
|
||||
}
|
||||
} else {
|
||||
|
@ -531,11 +627,22 @@ func (s *Strategy) DrawIndicators(time types.Time, priceLine types.SeriesExtend,
|
|||
|
||||
func (s *Strategy) DrawPNL(profit types.Series) *types.Canvas {
|
||||
canvas := types.NewCanvas(s.InstanceID())
|
||||
log.Errorf("pnl Highest: %f, Lowest: %f", types.Highest(profit, profit.Length()), types.Lowest(profit, profit.Length()))
|
||||
length := profit.Length()
|
||||
if s.GraphPNLDeductFee {
|
||||
canvas.PlotRaw("pnl % (with Fee Deducted)", profit, profit.Length())
|
||||
canvas.PlotRaw("pnl % (with Fee Deducted)", profit, length)
|
||||
} else {
|
||||
canvas.PlotRaw("pnl %", profit, profit.Length())
|
||||
canvas.PlotRaw("pnl %", profit, length)
|
||||
}
|
||||
canvas.YAxis = chart.YAxis{
|
||||
ValueFormatter: func(v interface{}) string {
|
||||
if vf, isFloat := v.(float64); isFloat {
|
||||
return fmt.Sprintf("%.4f", vf)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
canvas.PlotRaw("1", types.NumberSeries(1), length)
|
||||
return canvas
|
||||
}
|
||||
|
||||
|
@ -546,6 +653,14 @@ func (s *Strategy) DrawCumPNL(cumProfit types.Series) *types.Canvas {
|
|||
} else {
|
||||
canvas.PlotRaw("cummulative pnl %", cumProfit, cumProfit.Length())
|
||||
}
|
||||
canvas.YAxis = chart.YAxis{
|
||||
ValueFormatter: func(v interface{}) string {
|
||||
if vf, isFloat := v.(float64); isFloat {
|
||||
return fmt.Sprintf("%.4f", vf)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
return canvas
|
||||
}
|
||||
|
||||
|
@ -762,23 +877,39 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
if !ok {
|
||||
panic(fmt.Sprintf("cannot find order: %v", trade))
|
||||
}
|
||||
bp := buyPrice
|
||||
vol := Volume
|
||||
sp := sellPrice
|
||||
if tag == "close" {
|
||||
if !buyPrice.IsZero() {
|
||||
if trade.Side == types.SideTypeSell {
|
||||
if trade.Quantity.Compare(Volume) > 0 {
|
||||
profit.Update(modify(trade.Price.Div(buyPrice)).Float64())
|
||||
} else {
|
||||
profit.Update(modify(trade.Price.Div(buyPrice)).
|
||||
Sub(fixedpoint.One).
|
||||
Mul(trade.Quantity).
|
||||
Div(Volume).
|
||||
Add(fixedpoint.One).
|
||||
Float64())
|
||||
}
|
||||
cumProfit.Update(cumProfit.Last() * profit.Last())
|
||||
Volume = Volume.Sub(trade.Quantity)
|
||||
if Volume.IsZero() {
|
||||
if Volume.Sign() < 0 {
|
||||
sellPrice = trade.Price
|
||||
buyPrice = fixedpoint.Zero
|
||||
} else if Volume.Sign() == 0 {
|
||||
buyPrice = fixedpoint.Zero
|
||||
}
|
||||
if !sellPrice.IsZero() {
|
||||
panic("sellprice shouldn't be zero")
|
||||
} else {
|
||||
buyPrice = buyPrice.Mul(Volume).Add(trade.Price.Mul(trade.Quantity)).Div(Volume.Add(trade.Quantity))
|
||||
Volume = Volume.Add(trade.Quantity)
|
||||
}
|
||||
} else if !sellPrice.IsZero() {
|
||||
if trade.Side == types.SideTypeBuy {
|
||||
if trade.Quantity.Compare(Volume.Neg()) > 0 {
|
||||
profit.Update(modify(sellPrice.Div(trade.Price)).Float64())
|
||||
} else {
|
||||
profit.Update(modify(sellPrice.Div(trade.Price)).
|
||||
Sub(fixedpoint.One).
|
||||
Mul(trade.Quantity).
|
||||
|
@ -786,22 +917,46 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
Neg().
|
||||
Add(fixedpoint.One).
|
||||
Float64())
|
||||
}
|
||||
cumProfit.Update(cumProfit.Last() * profit.Last())
|
||||
Volume = Volume.Add(trade.Quantity)
|
||||
if Volume.IsZero() {
|
||||
if Volume.Sign() > 0 {
|
||||
buyPrice = trade.Price
|
||||
sellPrice = fixedpoint.Zero
|
||||
} else if Volume.Sign() == 0 {
|
||||
sellPrice = fixedpoint.Zero
|
||||
}
|
||||
if !buyPrice.IsZero() {
|
||||
panic("buyprice shouldn't be zero")
|
||||
} else {
|
||||
sellPrice = sellPrice.Mul(Volume).Sub(trade.Price.Mul(trade.Quantity)).Div(Volume.Sub(trade.Quantity))
|
||||
Volume = Volume.Sub(trade.Quantity)
|
||||
}
|
||||
} else {
|
||||
// position changed by strategy
|
||||
if trade.Side == types.SideTypeBuy {
|
||||
buyPrice = trade.Price
|
||||
Volume = Volume.Add(trade.Quantity)
|
||||
if Volume.Sign() > 0 {
|
||||
buyPrice = trade.Price
|
||||
sellPrice = fixedpoint.Zero
|
||||
} else if Volume.Sign() < 0 {
|
||||
sellPrice = trade.Price
|
||||
buyPrice = fixedpoint.Zero
|
||||
} else {
|
||||
buyPrice = fixedpoint.Zero
|
||||
sellPrice = fixedpoint.Zero
|
||||
}
|
||||
} else if trade.Side == types.SideTypeSell {
|
||||
sellPrice = trade.Price
|
||||
Volume = Volume.Sub(trade.Quantity)
|
||||
if Volume.Sign() > 0 {
|
||||
buyPrice = trade.Price
|
||||
sellPrice = fixedpoint.Zero
|
||||
} else if Volume.Sign() < 0 {
|
||||
sellPrice = trade.Price
|
||||
buyPrice = fixedpoint.Zero
|
||||
} else {
|
||||
buyPrice = fixedpoint.Zero
|
||||
sellPrice = fixedpoint.Zero
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if tag == "short" {
|
||||
|
@ -852,6 +1007,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.highestPrice = s.buyPrice
|
||||
s.sellPrice = sellPrice.Float64()
|
||||
s.lowestPrice = s.sellPrice
|
||||
bbgo.Notify("tag %s %v %s volafter: %v, quantity: %v, bp: %v, sp: %v, volbefore: %v, bpafter: %v, spafter: %v", tag, trade.Price, trade.Side, Volume, trade.Quantity, bp, sp, vol, s.buyPrice, s.sellPrice)
|
||||
})
|
||||
|
||||
dynamicKLine := &types.KLine{}
|
||||
|
@ -904,7 +1060,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
bbgo.RegisterCommand("/config", "Show latest config", func(reply interact.Reply) {
|
||||
var buffer bytes.Buffer
|
||||
s.Print(&buffer)
|
||||
s.Print(&buffer, false)
|
||||
reply.Message(buffer.String())
|
||||
})
|
||||
|
||||
|
@ -952,24 +1108,25 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.highestPrice = highf
|
||||
}
|
||||
avg := s.buyPrice + s.sellPrice
|
||||
stoploss = s.StopLoss.Float64()
|
||||
|
||||
exitShortCondition := ( /*avg+atr/2 <= highf || avg*(1.+stoploss) <= pricef || (drift > 0 || ddrift > DDriftFilterPos) ||*/ avg-atr*takeProfitFactor >= pricef ||
|
||||
exitShortCondition := ( /*avg*(1.+stoploss) <= pricef || (drift > 0 || ddrift > DDriftFilterPos) ||*/ avg-atr*takeProfitFactor >= pricef ||
|
||||
s.trailingCheck(highf, "short")) &&
|
||||
(s.Position.IsShort() && !s.Position.IsDust(price))
|
||||
exitLongCondition := ( /*avg-atr/2 >= lowf || avg*(1.-stoploss) >= pricef || (drift < 0 || ddrift < DDriftFilterNeg) ||*/ avg+atr*takeProfitFactor <= pricef ||
|
||||
exitLongCondition := ( /*avg*(1.-stoploss) >= pricef || (drift < 0 || ddrift < DDriftFilterNeg) ||*/ avg+atr*takeProfitFactor <= pricef ||
|
||||
s.trailingCheck(lowf, "long")) &&
|
||||
(s.Position.IsLong() && !s.Position.IsDust(price))
|
||||
if exitShortCondition || exitLongCondition {
|
||||
if exitLongCondition && s.highestPrice > avg {
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 4)
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 1.5)
|
||||
} else if exitShortCondition && avg > s.lowestPrice {
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 4)
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 1.5)
|
||||
}
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
}
|
||||
return
|
||||
}
|
||||
if kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
dynamicKLine.Set(&kline)
|
||||
|
||||
source := s.getSource(dynamicKLine)
|
||||
|
@ -995,6 +1152,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.stdevLow.Update(lowdiff)
|
||||
highdiff := highf - s.ma.Last()
|
||||
s.stdevHigh.Update(highdiff)
|
||||
|
||||
//log.Errorf("highdiff: %3.2f ma: %.2f, close: %8v, high: %8v, low: %8v, time: %v", s.stdevHigh.Last(), s.ma.Last(), kline.Close, kline.High, kline.Low, kline.StartTime)
|
||||
if s.lowestPrice > 0 && lowf < s.lowestPrice {
|
||||
s.lowestPrice = lowf
|
||||
}
|
||||
|
@ -1008,23 +1167,24 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
s.Rebalance(ctx, orderTagHistory)
|
||||
}
|
||||
|
||||
if !s.IsBackTesting() {
|
||||
//if !s.IsBackTesting() {
|
||||
balances := s.GeneralOrderExecutor.Session().GetAccount().Balances()
|
||||
bbgo.Notify("zeroPoint: %.4f, source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f",
|
||||
zeroPoint, sourcef, pricef, driftPred, drift[0], drift[1], atr, avg)
|
||||
bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, ddriftPred: %.4f, drift[1]: %.4f, ddrift[1]: %.4f, atr: %.4f, avg: %.4f, takeProfitFact: %.4f, lowf %.4f, highf: %.4f",
|
||||
sourcef, pricef, driftPred, ddriftPred, drift[1], ddrift[1], atr, avg, takeProfitFactor, lowf, highf)
|
||||
// Notify will parse args to strings and process separately
|
||||
bbgo.Notify("balances: [Base] %s [Quote] %s", balances[s.Market.BaseCurrency].String(), balances[s.Market.QuoteCurrency].String())
|
||||
}
|
||||
bbgo.Notify("balances: [Base] %s(%vU) [Quote] %s",
|
||||
balances[s.Market.BaseCurrency].String(),
|
||||
balances[s.Market.BaseCurrency].Total().Mul(price),
|
||||
balances[s.Market.QuoteCurrency].String())
|
||||
//}
|
||||
|
||||
drift1m := s.drift1m.Predict(3)
|
||||
|
||||
shortCondition := (drift[1] >= DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= DDriftFilterNeg || ddriftPred <= 0)
|
||||
longCondition := (drift[1] <= DriftFilterPos || ddrift[1] <= 0) && (driftPred >= DDriftFilterPos || ddriftPred >= 0)
|
||||
exitShortCondition := ((drift[0] >= DDriftFilterPos || ddrift[0] >= 0) && drift1m > 0 ||
|
||||
shortCondition := (drift[1] >= DriftFilterNeg || ddrift[1] >= 0) && (driftPred <= DDriftFilterNeg || ddriftPred <= 0) || drift[1] < 0 && drift[0] < 0
|
||||
longCondition := (drift[1] <= DriftFilterPos || ddrift[1] <= 0) && (driftPred >= DDriftFilterPos || ddriftPred >= 0) || drift[1] > 0 && drift[0] > 0
|
||||
exitShortCondition := ((drift[0] >= DDriftFilterPos || ddrift[0] >= 0) ||
|
||||
avg*(1.+stoploss) <= pricef ||
|
||||
avg-atr*takeProfitFactor >= pricef) &&
|
||||
s.Position.IsShort()
|
||||
exitLongCondition := ((drift[0] <= DDriftFilterNeg || ddrift[0] <= 0) && drift1m < 0 ||
|
||||
exitLongCondition := ((drift[0] <= DDriftFilterNeg || ddrift[0] <= 0) ||
|
||||
avg*(1.-stoploss) >= pricef ||
|
||||
avg+atr*takeProfitFactor <= pricef) &&
|
||||
s.Position.IsLong()
|
||||
|
@ -1035,9 +1195,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return
|
||||
}
|
||||
if exitShortCondition && avg > s.lowestPrice {
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 4)
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 1.5)
|
||||
} else if exitLongCondition && avg < s.highestPrice {
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 4)
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 1.5)
|
||||
}
|
||||
if s.takeProfitFactor.Last() == 0 {
|
||||
log.Errorf("exit %f %f %f %v", s.highestPrice, s.lowestPrice, avg, s.takeProfitFactor.Array(10))
|
||||
|
@ -1065,10 +1225,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return
|
||||
}
|
||||
if avg < s.highestPrice && avg > 0 && s.Position.IsLong() {
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 4)
|
||||
if s.takeProfitFactor.Last() == 0 {
|
||||
log.Errorf("short %f %f", s.highestPrice, avg)
|
||||
}
|
||||
s.takeProfitFactor.Update((s.highestPrice - avg) / atr * 1.5)
|
||||
}
|
||||
// Cleanup pending StopOrders
|
||||
quantity := baseBalance.Available
|
||||
|
@ -1108,11 +1265,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
return
|
||||
}
|
||||
if avg > s.lowestPrice && s.Position.IsShort() {
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 4)
|
||||
if s.takeProfitFactor.Last() == 0 {
|
||||
log.Errorf("long %f %f", s.lowestPrice, avg)
|
||||
}
|
||||
|
||||
s.takeProfitFactor.Update((avg - s.lowestPrice) / atr * 1.5)
|
||||
}
|
||||
quantity := quoteBalance.Available.Div(source)
|
||||
createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
|
@ -1134,9 +1287,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
|
||||
defer s.Print(os.Stdout, true)
|
||||
var buffer bytes.Buffer
|
||||
|
||||
defer fmt.Fprintln(os.Stdout, s.TradeStats.BriefString())
|
||||
s.Print(&buffer, true, true)
|
||||
fmt.Fprintln(&buffer, s.TradeStats.BriefString())
|
||||
|
||||
os.Stdout.Write(buffer.Bytes())
|
||||
|
||||
if s.GenerateGraph {
|
||||
s.Draw(dynamicKLine.StartTime, priceLine, &profit, &cumProfit, zeroPoints)
|
||||
|
|
|
@ -1198,20 +1198,24 @@ func expand(a []float64, length int, defaultVal float64) []float64 {
|
|||
return a
|
||||
}
|
||||
for i := 0; i < length-l; i++ {
|
||||
a = append(a, defaultVal)
|
||||
a = append([]float64{defaultVal}, a...)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (canvas *Canvas) Plot(tag string, a Series, endTime Time, length int) {
|
||||
func (canvas *Canvas) Plot(tag string, a Series, endTime Time, length int, intervals ...Interval) {
|
||||
var timeline []time.Time
|
||||
e := endTime.Time()
|
||||
if a.Length() == 0 {
|
||||
return
|
||||
}
|
||||
oldest := a.Index(a.Length() - 1)
|
||||
interval := canvas.Interval
|
||||
if len(intervals) > 0 {
|
||||
interval = intervals[0]
|
||||
}
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
shiftedT := e.Add(-time.Duration(i*canvas.Interval.Minutes()) * time.Minute)
|
||||
shiftedT := e.Add(-time.Duration(i*interval.Minutes()) * time.Minute)
|
||||
timeline = append(timeline, shiftedT)
|
||||
}
|
||||
canvas.Series = append(canvas.Series, chart.TimeSeries{
|
||||
|
|
Loading…
Reference in New Issue
Block a user