diff --git a/config/drift.yaml b/config/drift.yaml index e3c249bc4..a91314ac1 100644 --- a/config/drift.yaml +++ b/config/drift.yaml @@ -21,15 +21,14 @@ exchangeStrategies: # kline interval for indicators interval: 15m window: 2 - stoploss: 2% + stoploss: 0.3% source: close - predictOffset: 3 + predictOffset: 2 # position avg +- takeProfitFactor * atr as take profit price - takeProfitFactor: 1 - noStopPrice: true - noTrailingStopLoss: false + takeProfitFactor: 1.4 + noTrailingStopLoss: true # stddev on high/low-source - hlVarianceMultiplier: 0.34 + hlVarianceMultiplier: 0.22 generateGraph: true graphPNLDeductFee: false diff --git a/config/driftBTC.yaml b/config/driftBTC.yaml new file mode 100644 index 000000000..45d7a7099 --- /dev/null +++ b/config/driftBTC.yaml @@ -0,0 +1,91 @@ +--- +persistence: + redis: + host: 127.0.0.1 + port: 6379 + db: 0 + +sessions: + binance: + exchange: binance + futures: false + envVarPrefix: binance + heikinAshi: false + +exchangeStrategies: + +- on: binance + drift: + canvasPath: "./output.png" + symbol: BTCBUSD + # kline interval for indicators + interval: 15m + window: 2 + stoploss: 0.3% + source: close + predictOffset: 2 + # position avg +- takeProfitFactor * atr as take profit price + takeProfitFactor: 1.2 + noTrailingStopLoss: true + # stddev on high/low-source + hlVarianceMultiplier: 0.27 + + generateGraph: true + graphPNLDeductFee: true + graphPNLPath: "./pnl.png" + graphCumPNLPath: "./cumpnl.png" + exits: + #- roiStopLoss: + # percentage: 0.8% + #- roiTakeProfit: + # percentage: 3% + #- protectiveStopLoss: + # activationRatio: 0.5% + # stopLossRatio: 0.1% + # placeStopOrder: false + - trailingStop: + callbackRate: 1% + # activationRatio is relative to the average cost, + # when side is buy, 1% means lower 1% than the average cost. + # when side is sell, 1% means higher 1% than the average cost. + activationRatio: 3% + # minProfit uses the position ROI to calculate the profit ratio + minProfit: 1% + interval: 1m + side: buy + closePosition: 100% + #- protectiveStopLoss: + # activationRatio: 5% + # stopLossRatio: 1% + # placeStopOrder: false + #- cumulatedVolumeTakeProfit: + # interval: 5m + # window: 2 + # minQuoteVolume: 200_000_000 + #- protectiveStopLoss: + # activationRatio: 2% + # stopLossRatio: 1% + # placeStopOrder: false + +sync: + userDataStream: + trades: true + filledOrders: true + sessions: + - binance + symbols: + - BTCBUSD + +backtest: + startTime: "2022-01-01" + endTime: "2022-06-18" + symbols: + - BTCBUSD + sessions: [binance] + accounts: + binance: + makerFeeRate: 0.000 + takerFeeRate: 0.00075 + balances: + BTC: 10 + BUSD: 5000.0 diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 2b1bbc332..4c1a9b6e2 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -130,6 +130,28 @@ func New(key, secret string) *Exchange { log.WithError(err).Error("can not set server time") } }) + go func() { + ticker := time.NewTicker(time.Hour) + defer ticker.Stop() + for { + select { + case <-ticker.C: + _, err = client.NewSetServerTimeService().Do(context.Background()) + if err != nil { + log.WithError(err).Error("can not set server time") + } + + _, err = futuresClient.NewSetServerTimeService().Do(context.Background()) + if err != nil { + log.WithError(err).Error("can not set server time") + } + + if err = client2.SetTimeOffsetFromServer(context.Background()); err != nil { + log.WithError(err).Error("can not set server time") + } + } + } + }() } return ex diff --git a/pkg/indicator/boll.go b/pkg/indicator/boll.go index 9b0a54704..f29fb714a 100644 --- a/pkg/indicator/boll.go +++ b/pkg/indicator/boll.go @@ -21,7 +21,6 @@ Bollinger Bands Technical indicator guide: //go:generate callbackgen -type BOLL type BOLL struct { - types.SeriesBase types.IntervalWindow // K is the multiplier of Std, generally it's 2 @@ -74,7 +73,6 @@ func (inc *BOLL) LastDownBand() float64 { func (inc *BOLL) Update(value float64) { if inc.SMA == nil { - inc.SeriesBase.Series = inc inc.SMA = &SMA{IntervalWindow: inc.IntervalWindow} } diff --git a/pkg/indicator/drift.go b/pkg/indicator/drift.go index 32490d3ec..1146bfddc 100644 --- a/pkg/indicator/drift.go +++ b/pkg/indicator/drift.go @@ -47,6 +47,27 @@ func (inc *Drift) Update(value float64) { } } +// Assume that MA is SMA +func (inc *Drift) ZeroPoint() float64 { + window := float64(inc.Window) + stdev := types.Stdev(inc.chng, inc.Window) + chng := inc.chng.Index(inc.Window - 1) + /*b := -2 * inc.MA.Last() - 2 + c := window * stdev * stdev - chng * chng + 2 * chng * (inc.MA.Last() + 1) - 2 * inc.MA.Last() * window + + root := math.Sqrt(b*b - 4*c) + K1 := (-b + root)/2 + K2 := (-b - root)/2 + N1 := math.Exp(K1) * inc.LastValue + N2 := math.Exp(K2) * inc.LastValue + if math.Abs(inc.LastValue-N1) < math.Abs(inc.LastValue-N2) { + return N1 + } else { + return N2 + }*/ + return inc.LastValue * math.Exp(window*(0.5*stdev*stdev)+chng-inc.MA.Last()*window) +} + func (inc *Drift) Clone() (out *Drift) { out = &Drift{ IntervalWindow: inc.IntervalWindow, diff --git a/pkg/indicator/fisher.go b/pkg/indicator/fisher.go new file mode 100644 index 000000000..4cb98f4bc --- /dev/null +++ b/pkg/indicator/fisher.go @@ -0,0 +1,58 @@ +package indicator + +import ( + "math" + + "github.com/c9s/bbgo/pkg/types" +) + +//go:generate callbackgen -type FisherTransform +type FisherTransform struct { + types.SeriesBase + types.IntervalWindow + prices *types.Queue + Values types.Float64Slice + + UpdateCallbacks []func(value float64) +} + +func (inc *FisherTransform) Update(value float64) { + if inc.prices == nil { + inc.prices = types.NewQueue(inc.Window) + inc.SeriesBase.Series = inc + } + inc.prices.Update(value) + highest := inc.prices.Highest(inc.Window) + lowest := inc.prices.Lowest(inc.Window) + x := 2*((value-lowest)/(highest-lowest)) - 1 + if x == 1 { + x = 0.9999 + } else if x == -1 { + x = -0.9999 + } + inc.Values.Update(0.5 * math.Log((1+x)/(1-x))) + if len(inc.Values) > MaxNumOfEWMA { + inc.Values = inc.Values[MaxNumOfEWMATruncateSize-1:] + } +} + +func (inc *FisherTransform) Last() float64 { + if inc.Values == nil { + return 0.0 + } + return inc.Values.Last() +} + +func (inc *FisherTransform) Index(i int) float64 { + if inc.Values == nil { + return 0.0 + } + return inc.Values.Index(i) +} + +func (inc *FisherTransform) Length() int { + if inc.Values == nil { + return 0 + } + return inc.Values.Length() +} diff --git a/pkg/strategy/drift/strategy.go b/pkg/strategy/drift/strategy.go index b7fd35a3e..81abf8e01 100644 --- a/pkg/strategy/drift/strategy.go +++ b/pkg/strategy/drift/strategy.go @@ -51,7 +51,7 @@ type Strategy struct { ma types.UpdatableSeriesExtend stdevHigh *indicator.StdDev stdevLow *indicator.StdDev - drift *indicator.Drift + drift *DriftMA atr *indicator.ATR midPrice fixedpoint.Value lock sync.RWMutex @@ -62,11 +62,12 @@ type Strategy struct { CanvasPath string `json:"canvasPath"` PredictOffset int `json:"predictOffset"` HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier"` - NoStopPrice bool `json:"noStopPrice"` NoTrailingStopLoss bool `json:"noTrailingStopLoss"` - buyPrice float64 - sellPrice float64 + buyPrice float64 + sellPrice float64 + highestPrice float64 + lowestPrice float64 // This is not related to trade but for statistics graph generation // Will deduct fee in percentage from every trade @@ -76,8 +77,6 @@ type Strategy struct { // Whether to generate graph when shutdown GenerateGraph bool `json:"generateGraph"` - StopOrders map[uint64]*types.SubmitOrder - ExitMethods bbgo.ExitMethodSet `json:"exits"` Session *bbgo.ExchangeSession *bbgo.GeneralOrderExecutor @@ -101,7 +100,6 @@ func (s *Strategy) Print(o *os.File) { hiyellow(f, "symbol: %s\n", s.Symbol) hiyellow(f, "interval: %s\n", s.Interval) hiyellow(f, "window: %d\n", s.Window) - hiyellow(f, "noStopPrice: %v\n", s.NoStopPrice) hiyellow(f, "noTrailingStopLoss: %v\n", s.NoTrailingStopLoss) hiyellow(f, "hlVarianceMutiplier: %f\n", s.HighLowVarianceMultiplier) hiyellow(f, "\n") @@ -112,7 +110,7 @@ func (s *Strategy) ID() string { } func (s *Strategy) InstanceID() string { - return fmt.Sprintf("%s:%s", ID, s.Symbol) + return fmt.Sprintf("%s-%s", ID, s.Symbol) } func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { @@ -138,15 +136,6 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu if order == nil { return nil } - if percentage.Compare(fixedpoint.One) == 0 { - // Cleanup pending StopOrders - s.StopOrders = make(map[uint64]*types.SubmitOrder) - } else { - // Should only have one stop order - for _, o := range s.StopOrders { - o.Quantity = o.Quantity.Mul(fixedpoint.One.Sub(percentage)) - } - } order.Tag = "close" order.TimeInForce = "" balances := s.Session.GetAccount().Balances() @@ -205,54 +194,52 @@ func (s *Strategy) SourceFuncGenerator() SourceFunc { } } -func (s *Strategy) BindStopLoss(ctx context.Context) { - s.StopOrders = make(map[uint64]*types.SubmitOrder) - s.Session.UserDataStream.OnOrderUpdate(func(order types.Order) { - if len(s.StopOrders) == 0 { - return - } - if order.Symbol != s.Symbol { - return - } - if order.Status == types.OrderStatusCanceled { - delete(s.StopOrders, order.OrderID) - return - } - if order.Status != types.OrderStatusFilled { - return - } - if o, ok := s.StopOrders[order.OrderID]; ok { - delete(s.StopOrders, order.OrderID) - if o.Side == types.SideTypeBuy { - quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency) - if !ok { - log.Errorf("unable to get quoteCurrency") - return - } - o.Quantity = quoteBalance.Available.Div(o.Price) - } else { - baseBalance, ok := s.Session.GetAccount().Balance(s.Market.BaseCurrency) - if !ok { - log.Errorf("unable to get baseCurrency") - return - } - o.Quantity = baseBalance.Available - } - if _, err := s.GeneralOrderExecutor.SubmitOrders(ctx, *o); err != nil { - log.WithError(err).Errorf("cannot send stop order: %v", order) - } - } - }) +type DriftMA struct { + types.SeriesBase + ma1 types.UpdatableSeries + drift *indicator.Drift + ma2 types.UpdatableSeries +} + +func (s *DriftMA) Update(value float64) { + s.ma1.Update(value) + s.drift.Update(s.ma1.Last()) + s.ma2.Update(s.drift.Last()) +} + +func (s *DriftMA) Last() float64 { + return s.ma2.Last() +} + +func (s *DriftMA) Index(i int) float64 { + return s.ma2.Index(i) +} + +func (s *DriftMA) Length() int { + return s.ma2.Length() +} + +func (s *DriftMA) ZeroPoint() float64 { + return s.drift.ZeroPoint() } func (s *Strategy) InitIndicators() error { - s.ma = &indicator.EWMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 5}} + s.ma = &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 5}} s.stdevHigh = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 6}} s.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 6}} - s.drift = &indicator.Drift{ - MA: &indicator.SMA{IntervalWindow: s.IntervalWindow}, - IntervalWindow: s.IntervalWindow, + s.drift = &DriftMA{ + drift: &indicator.Drift{ + MA: &indicator.SMA{IntervalWindow: s.IntervalWindow}, + IntervalWindow: s.IntervalWindow, + }, + ma1: &indicator.EWMA{ + IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 2}, + }, + ma2: &indicator.FisherTransform{ + IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 9}, + }, } + s.drift.SeriesBase.Series = s.drift s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: 14}} store, _ := s.Session.MarketDataStore(s.Symbol) klines, ok := store.KLinesOfInterval(s.Interval) @@ -299,10 +286,15 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) { } price = s.midPrice pricef = s.midPrice.Float64() - s.lock.Unlock() } else { return } + if s.highestPrice > 0 && s.highestPrice < pricef { + s.highestPrice = pricef + } + if s.lowestPrice > 0 && s.lowestPrice > pricef { + s.lowestPrice = pricef + } // for trailing stoploss during the realtime if s.NoTrailingStopLoss { @@ -311,9 +303,11 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) { atr = s.atr.Last() avg = s.buyPrice + s.sellPrice stoploss = s.StopLoss.Float64() - exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef) && + exitShortCondition := (avg+atr/2 <= pricef || avg*(1.+stoploss) <= pricef || avg-atr*s.TakeProfitFactor >= pricef || + ((pricef-s.lowestPrice)/pricef > stoploss && (s.sellPrice-s.lowestPrice)/s.sellPrice > 0.01)) && (s.Position.IsShort() && !s.Position.IsDust(price)) - exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || avg+atr*s.TakeProfitFactor <= pricef) && + exitLongCondition := (avg-atr/2 >= pricef || avg*(1.-stoploss) >= pricef || avg+atr*s.TakeProfitFactor <= pricef || + ((s.highestPrice-pricef)/pricef > stoploss && (s.highestPrice-s.buyPrice)/s.buyPrice > 0.01)) && (!s.Position.IsLong() && !s.Position.IsDust(price)) if exitShortCondition || exitLongCondition { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { @@ -322,6 +316,7 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) { } _ = s.ClosePosition(ctx, fixedpoint.One) } + s.lock.Unlock() }) s.getLastPrice = func() (lastPrice fixedpoint.Value) { @@ -343,19 +338,25 @@ func (s *Strategy) InitTickerFunctions(ctx context.Context) { } -func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series) { +func (s *Strategy) Draw(time types.Time, priceLine types.SeriesExtend, profit types.Series, cumProfit types.Series, zeroPoints types.Series) { canvas := types.NewCanvas(s.InstanceID(), s.Interval) Length := priceLine.Length() - if Length > 100 { - Length = 100 + if Length > 300 { + Length = 300 } mean := priceLine.Mean(Length) highestPrice := priceLine.Minus(mean).Abs().Highest(Length) highestDrift := s.drift.Abs().Highest(Length) - ratio := highestDrift / highestPrice - canvas.Plot("drift", s.drift, time, Length) - canvas.Plot("zero", types.NumberSeries(0), time, Length) - canvas.Plot("price", priceLine.Minus(mean).Mul(ratio), time, Length) + hi := s.drift.drift.Abs().Highest(Length) + ratio := highestPrice / highestDrift + 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("zero", types.NumberSeries(mean), time, Length) + canvas.Plot("price", priceLine, time, Length) + canvas.Plot("zeroPoint", zeroPoints, time, Length) f, err := os.Create(s.CanvasPath) if err != nil { log.WithError(err).Errorf("cannot create on %s", s.CanvasPath) @@ -532,26 +533,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se Volume = fixedpoint.Zero } Volume = Volume.Add(trade.Quantity) - } else if tag == "sl" { - // TODO: not properly handled for single order, multiple trades - if !buyPrice.IsZero() { - profit.Update(modify(trade.Price.Div(buyPrice)).Float64()) - cumProfit.Update(cumProfit.Last() * profit.Last()) - buyPrice = fixedpoint.Zero - } else if !sellPrice.IsZero() { - profit.Update(modify(sellPrice.Div(trade.Price)).Float64()) - cumProfit.Update(cumProfit.Last() * profit.Last()) - sellPrice = fixedpoint.Zero - } else { - panic("no position to sl") - } } s.buyPrice = buyPrice.Float64() + s.highestPrice = s.buyPrice s.sellPrice = sellPrice.Float64() + s.lowestPrice = s.sellPrice }) - s.BindStopLoss(ctx) - if err := s.InitIndicators(); err != nil { log.WithError(err).Errorf("InitIndicator failed") return nil @@ -559,7 +547,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.InitTickerFunctions(ctx) dynamicKLine := &types.KLine{} - priceLine := types.NewQueue(100) + priceLine := types.NewQueue(300) + zeroPoints := types.NewQueue(300) stoploss := s.StopLoss.Float64() session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { @@ -585,11 +574,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se pricef := price.Float64() lowf := math.Min(kline.Low.Float64(), pricef) highf := math.Max(kline.High.Float64(), pricef) + if s.lowestPrice > 0 && lowf < s.lowestPrice { + s.lowestPrice = lowf + } + if s.highestPrice > 0 && highf > s.highestPrice { + s.highestPrice = highf + } avg := s.buyPrice + s.sellPrice - exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) && + exitShortCondition := (avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf || + ((highf-s.lowestPrice)/pricef > stoploss && (s.sellPrice-s.lowestPrice)/s.sellPrice > 0.01)) && (s.Position.IsShort() && !s.Position.IsDust(price)) - exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) && + exitLongCondition := (avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf || + ((s.highestPrice-pricef)/pricef > stoploss && (s.highestPrice-s.buyPrice)/s.buyPrice > 0.01)) && (s.Position.IsLong() && !s.Position.IsDust(price)) if exitShortCondition || exitLongCondition { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { @@ -607,8 +604,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se priceLine.Update(sourcef) s.ma.Update(sourcef) s.drift.Update(sourcef) + zeroPoint := s.drift.ZeroPoint() + zeroPoints.Update(zeroPoint) s.atr.PushK(kline) drift = s.drift.Array(2) + ddrift := s.drift.drift.Array(2) driftPred = s.drift.Predict(s.PredictOffset) atr = s.atr.Last() price := s.getLastPrice() @@ -623,18 +623,22 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if !s.IsBackTesting() { balances := s.Session.GetAccount().Balances() - bbgo.Notify("source: %.4f, price: %.4f, driftPred: %.4f, drift: %.4f, drift[1]: %.4f, atr: %.4f, avg: %.4f", - sourcef, pricef, driftPred, drift[0], drift[1], atr, avg) + 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) // 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()) } - shortCondition := (driftPred <= 0 && drift[0] <= 0) - longCondition := (driftPred >= 0 && drift[0] >= 0) - exitShortCondition := ((drift[1] < 0 && drift[0] >= 0) || avg+atr/2 <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) && - (s.Position.IsShort() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition - exitLongCondition := ((drift[1] > 0 && drift[0] < 0) || avg-atr/2 >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) && - (s.Position.IsLong() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition + //shortCondition := (sourcef <= zeroPoint && driftPred <= drift[0] && drift[0] <= 0 && drift[1] > 0 && drift[2] > drift[1]) + //longCondition := (sourcef >= zeroPoint && driftPred >= drift[0] && drift[0] >= 0 && drift[1] < 0 && drift[2] < drift[1]) + //bothUp := ddrift[1] < ddrift[0] && drift[1] < drift[0] + //bothDown := ddrift[1] > ddrift[0] && drift[1] > drift[0] + shortCondition := (ddrift[0] <= 0 || drift[0] <= 0) && driftPred < 0. + longCondition := (ddrift[0] >= 0 || drift[0] >= 0) && driftPred > 0 + exitShortCondition := (avg+atr <= highf || avg*(1.+stoploss) <= highf || avg-atr*s.TakeProfitFactor >= lowf) && + (s.Position.IsShort() && !s.Position.IsDust(fixedpoint.Max(price, source))) && !longCondition && !shortCondition + exitLongCondition := (avg-atr >= lowf || avg*(1.-stoploss) >= lowf || avg+atr*s.TakeProfitFactor <= highf) && + (s.Position.IsLong() && !s.Position.IsDust(fixedpoint.Min(price, source))) && !shortCondition && !longCondition if exitShortCondition || exitLongCondition { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { @@ -642,6 +646,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } _ = s.ClosePosition(ctx, fixedpoint.One) + return } if shortCondition { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { @@ -663,18 +668,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } // Cleanup pending StopOrders - s.StopOrders = make(map[uint64]*types.SubmitOrder) quantity := baseBalance.Available - stopPrice := fixedpoint.NewFromFloat(math.Min(sourcef+atr/2, sourcef*(1.+stoploss))) - stopOrder := types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeBuy, - Type: types.OrderTypeStopLimit, - StopPrice: stopPrice, - Price: stopPrice, - Quantity: quantity, - Tag: "sl", - } createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{ Symbol: s.Symbol, Side: types.SideTypeSell, @@ -688,16 +682,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } orderTagHistory[createdOrders[0].OrderID] = "short" - if s.NoStopPrice { - return - } - if createdOrders[0].Status == types.OrderStatusFilled { - if o, err := s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder); err == nil { - orderTagHistory[o[0].OrderID] = "sl" - } - return - } - s.StopOrders[createdOrders[0].OrderID] = &stopOrder } if longCondition { if err := s.GeneralOrderExecutor.GracefulCancel(ctx); err != nil { @@ -709,6 +693,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se source = price } sourcef = source.Float64() + quoteBalance, ok := s.Session.GetAccount().Balance(s.Market.QuoteCurrency) if !ok { log.Errorf("unable to get quoteCurrency") @@ -718,20 +703,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se quoteBalance.Available.Div(source), source) { return } - // Cleanup pending StopOrders - s.StopOrders = make(map[uint64]*types.SubmitOrder) quantity := quoteBalance.Available.Div(source) - stopPrice := fixedpoint.NewFromFloat(math.Max(sourcef-atr/2, sourcef*(1.-stoploss))) - stopOrder := types.SubmitOrder{ - Symbol: s.Symbol, - Side: types.SideTypeSell, - Type: types.OrderTypeStopLimit, - TimeInForce: types.TimeInForceGTC, - StopPrice: stopPrice, - Price: stopPrice, - Quantity: quantity, - Tag: "sl", - } createdOrders, err := s.GeneralOrderExecutor.SubmitOrders(ctx, types.SubmitOrder{ Symbol: s.Symbol, Side: types.SideTypeBuy, @@ -745,16 +717,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } orderTagHistory[createdOrders[0].OrderID] = "long" - if s.NoStopPrice { - return - } - if createdOrders[0].Status == types.OrderStatusFilled { - if o, err := s.GeneralOrderExecutor.SubmitOrders(ctx, stopOrder); err == nil { - orderTagHistory[o[0].OrderID] = "sl" - } - return - } - s.StopOrders[createdOrders[0].OrderID] = &stopOrder } }) @@ -765,7 +727,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se defer fmt.Fprintln(os.Stdout, s.TradeStats.BriefString()) if s.GenerateGraph { - s.Draw(dynamicKLine.StartTime, priceLine, &profit, &cumProfit) + s.Draw(dynamicKLine.StartTime, priceLine, &profit, &cumProfit, zeroPoints) } wg.Done()