From 26d640ff3b8110c1890ccd916cea4c3454cd36cd Mon Sep 17 00:00:00 2001 From: austin362667 Date: Tue, 4 Oct 2022 14:24:41 +0800 Subject: [PATCH 1/3] strategy: fix irr --- config/irr.yaml | 9 ++++----- pkg/strategy/irr/strategy.go | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/config/irr.yaml b/config/irr.yaml index 54cbe452e..e307463b1 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -13,22 +13,21 @@ sessions: exchangeStrategies: - on: binance - oneliner: + irr: symbol: BTCBUSD - interval: 1m + interval: 1s window: 120 amount: 5_000.0 backtest: sessions: - binance - startTime: "2021-01-01" - endTime: "2022-09-30" + startTime: "2022-10-01" + endTime: "2022-10-04" symbols: - BTCBUSD accounts: binance: takerFeeRate: 0.0 balances: -# ETH: 1 BUSD: 5_000.0 diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index 2c77a1b3f..a666f169e 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -213,9 +213,21 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // use kline direction to prevent reversing position too soon if diffQty.Sign() > 0 { // && kline.Direction() >= 0 - s.orderExecutor.OpenPosition(context.Background(), bbgo.OpenPositionOptions{Quantity: diffQty.Abs(), Long: true, LimitOrder: false}) + _, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeBuy, + Quantity: diffQty.Abs(), + Type: types.OrderTypeMarket, + Tag: "irr buy more", + }) } else if diffQty.Sign() < 0 { // && kline.Direction() <= 0 - s.orderExecutor.OpenPosition(context.Background(), bbgo.OpenPositionOptions{Quantity: diffQty.Abs(), Short: true, LimitOrder: false}) + _, _ = s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Quantity: diffQty.Abs(), + Type: types.OrderTypeMarket, + Tag: "irr sell more", + }) } })) From 3c52e9e145b592ada1fca001c2b1e6464db59048 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Tue, 4 Oct 2022 14:59:09 +0800 Subject: [PATCH 2/3] strategy: refactor draw lib --- config/irr.yaml | 8 +- pkg/strategy/irr/draw.go | 90 +++++++ pkg/strategy/irr/strategy.go | 450 +++++++++++++++++------------------ 3 files changed, 314 insertions(+), 234 deletions(-) create mode 100644 pkg/strategy/irr/draw.go diff --git a/config/irr.yaml b/config/irr.yaml index e307463b1..5aa782bc5 100644 --- a/config/irr.yaml +++ b/config/irr.yaml @@ -15,14 +15,18 @@ exchangeStrategies: - on: binance irr: symbol: BTCBUSD - interval: 1s + interval: 1m window: 120 amount: 5_000.0 + # Draw pnl + drawGraph: true + graphPNLPath: "./pnl.png" + graphCumPNLPath: "./cumpnl.png" backtest: sessions: - binance - startTime: "2022-10-01" + startTime: "2022-09-01" endTime: "2022-10-04" symbols: - BTCBUSD diff --git a/pkg/strategy/irr/draw.go b/pkg/strategy/irr/draw.go new file mode 100644 index 000000000..ffa525bfa --- /dev/null +++ b/pkg/strategy/irr/draw.go @@ -0,0 +1,90 @@ +package irr + +import ( + "bytes" + "fmt" + "os" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/interact" + "github.com/c9s/bbgo/pkg/types" + "github.com/wcharczuk/go-chart/v2" +) + +func (s *Strategy) InitDrawCommands(profit, cumProfit types.Series) { + bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) { + canvas := DrawPNL(s.InstanceID(), profit) + var buffer bytes.Buffer + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render pnl in drift") + reply.Message(fmt.Sprintf("[error] cannot render pnl in ewo: %v", err)) + return + } + bbgo.SendPhoto(&buffer) + }) + bbgo.RegisterCommand("/cumpnl", "Draw Cummulative PNL(Quote)", func(reply interact.Reply) { + canvas := DrawCumPNL(s.InstanceID(), cumProfit) + var buffer bytes.Buffer + if err := canvas.Render(chart.PNG, &buffer); err != nil { + log.WithError(err).Errorf("cannot render cumpnl in drift") + reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err)) + return + } + bbgo.SendPhoto(&buffer) + }) +} + +func (s *Strategy) Draw(profit, cumProfit types.Series) error { + + canvas := DrawPNL(s.InstanceID(), profit) + f, err := os.Create(s.GraphPNLPath) + if err != nil { + return fmt.Errorf("cannot create on path " + s.GraphPNLPath) + } + defer f.Close() + if err = canvas.Render(chart.PNG, f); err != nil { + return fmt.Errorf("cannot render pnl") + } + canvas = DrawCumPNL(s.InstanceID(), cumProfit) + f, err = os.Create(s.GraphCumPNLPath) + if err != nil { + return fmt.Errorf("cannot create on path " + s.GraphCumPNLPath) + } + defer f.Close() + if err = canvas.Render(chart.PNG, f); err != nil { + return fmt.Errorf("cannot render cumpnl") + } + + return nil +} + +func DrawPNL(instanceID string, profit types.Series) *types.Canvas { + canvas := types.NewCanvas(instanceID) + length := profit.Length() + log.Infof("pnl Highest: %f, Lowest: %f", types.Highest(profit, length), types.Lowest(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 +} + +func DrawCumPNL(instanceID string, cumProfit types.Series) *types.Canvas { + canvas := types.NewCanvas(instanceID) + 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 +} diff --git a/pkg/strategy/irr/strategy.go b/pkg/strategy/irr/strategy.go index a666f169e..75e5910d6 100644 --- a/pkg/strategy/irr/strategy.go +++ b/pkg/strategy/irr/strategy.go @@ -1,23 +1,18 @@ package irr import ( - "bytes" "context" - "errors" "fmt" - "os" - "sync" - "time" - "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/data/tsv" "github.com/c9s/bbgo/pkg/datatype/floats" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/indicator" - "github.com/c9s/bbgo/pkg/interact" "github.com/c9s/bbgo/pkg/types" + "os" + "sync" "github.com/sirupsen/logrus" - "github.com/wcharczuk/go-chart/v2" ) const ID = "irr" @@ -57,53 +52,145 @@ type Strategy struct { // StrategyController bbgo.StrategyController - // plotting - bbgo.SourceSelector - alpha *NRR - priceLines *types.Queue - trendLine types.UpdatableSeriesExtend - ma types.UpdatableSeriesExtend - stdevHigh *indicator.StdDev - stdevLow *indicator.StdDev - atr *indicator.ATR - midPrice fixedpoint.Value - lock sync.RWMutex `ignore:"true"` - positionLock sync.RWMutex `ignore:"true"` - startTime time.Time - minutesCounter int - orderPendingCounter map[uint64]int - frameKLine *types.KLine - kline1m *types.KLine + AccountValueCalculator *bbgo.AccountValueCalculator - beta float64 + // whether to draw graph or not by the end of backtest + DrawGraph bool `json:"drawGraph"` + GraphPNLPath string `json:"graphPNLPath"` + GraphCumPNLPath string `json:"graphCumPNLPath"` - StopLoss fixedpoint.Value `json:"stoploss"` - CanvasPath string `json:"canvasPath"` - PredictOffset int `json:"predictOffset"` - HighLowVarianceMultiplier float64 `json:"hlVarianceMultiplier"` - 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"` - FisherTransformWindow1m int `json:"fisherTransformWindow1m"` - SmootherWindow1m int `json:"smootherWindow1m"` - SmootherWindow int `json:"smootherWindow"` - FisherTransformWindow int `json:"fisherTransformWindow"` - ATRWindow int `json:"atrWindow"` - PendingMinutes int `json:"pendingMinutes"` // if order not be traded for pendingMinutes of time, cancel it. - NoRebalance bool `json:"noRebalance"` // disable rebalance - 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 `json:"trailingActivationRatio"` + // for position + buyPrice float64 `persistence:"buy_price"` + sellPrice float64 `persistence:"sell_price"` + highestPrice float64 `persistence:"highest_price"` + lowestPrice float64 `persistence:"lowest_price"` - // This is not related to trade but for statistics graph generation - // Will deduct fee in percentage from every trade - GraphPNLDeductFee bool `json:"graphPNLDeductFee"` - GraphPNLPath string `json:"graphPNLPath"` - GraphCumPNLPath string `json:"graphCumPNLPath"` - // Whether to generate graph when shutdown - GenerateGraph bool `json:"generateGraph"` + // Accumulated profit report + AccumulatedProfitReport *AccumulatedProfitReport `json:"accumulatedProfitReport"` +} + +// AccumulatedProfitReport For accumulated profit report output +type AccumulatedProfitReport struct { + // AccumulatedProfitMAWindow Accumulated profit SMA window, in number of trades + AccumulatedProfitMAWindow int `json:"accumulatedProfitMAWindow"` + + // IntervalWindow interval window, in days + IntervalWindow int `json:"intervalWindow"` + + // NumberOfInterval How many intervals to output to TSV + NumberOfInterval int `json:"NumberOfInterval"` + + // TsvReportPath The path to output report to + TsvReportPath string `json:"tsvReportPath"` + + // AccumulatedDailyProfitWindow The window to sum up the daily profit, in days + AccumulatedDailyProfitWindow int `json:"accumulatedDailyProfitWindow"` + + // Accumulated profit + accumulatedProfit fixedpoint.Value + accumulatedProfitPerDay floats.Slice + previousAccumulatedProfit fixedpoint.Value + + // Accumulated profit MA + accumulatedProfitMA *indicator.SMA + accumulatedProfitMAPerDay floats.Slice + + // Daily profit + dailyProfit floats.Slice + + // Accumulated fee + accumulatedFee fixedpoint.Value + accumulatedFeePerDay floats.Slice + + // Win ratio + winRatioPerDay floats.Slice + + // Profit factor + profitFactorPerDay floats.Slice + + // Trade number + dailyTrades floats.Slice + accumulatedTrades int + previousAccumulatedTrades int +} + +func (r *AccumulatedProfitReport) Initialize() { + if r.AccumulatedProfitMAWindow <= 0 { + r.AccumulatedProfitMAWindow = 60 + } + if r.IntervalWindow <= 0 { + r.IntervalWindow = 7 + } + if r.AccumulatedDailyProfitWindow <= 0 { + r.AccumulatedDailyProfitWindow = 7 + } + if r.NumberOfInterval <= 0 { + r.NumberOfInterval = 1 + } + r.accumulatedProfitMA = &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: types.Interval1d, Window: r.AccumulatedProfitMAWindow}} +} + +func (r *AccumulatedProfitReport) RecordProfit(profit fixedpoint.Value) { + r.accumulatedProfit = r.accumulatedProfit.Add(profit) +} + +func (r *AccumulatedProfitReport) RecordTrade(fee fixedpoint.Value) { + r.accumulatedFee = r.accumulatedFee.Add(fee) + r.accumulatedTrades += 1 +} + +func (r *AccumulatedProfitReport) DailyUpdate(tradeStats *types.TradeStats) { + // Daily profit + r.dailyProfit.Update(r.accumulatedProfit.Sub(r.previousAccumulatedProfit).Float64()) + r.previousAccumulatedProfit = r.accumulatedProfit + + // Accumulated profit + r.accumulatedProfitPerDay.Update(r.accumulatedProfit.Float64()) + + // Accumulated profit MA + r.accumulatedProfitMA.Update(r.accumulatedProfit.Float64()) + r.accumulatedProfitMAPerDay.Update(r.accumulatedProfitMA.Last()) + + // Accumulated Fee + r.accumulatedFeePerDay.Update(r.accumulatedFee.Float64()) + + // Win ratio + r.winRatioPerDay.Update(tradeStats.WinningRatio.Float64()) + + // Profit factor + r.profitFactorPerDay.Update(tradeStats.ProfitFactor.Float64()) + + // Daily trades + r.dailyTrades.Update(float64(r.accumulatedTrades - r.previousAccumulatedTrades)) + r.previousAccumulatedTrades = r.accumulatedTrades +} + +// Output Accumulated profit report to a TSV file +func (r *AccumulatedProfitReport) Output(symbol string) { + if r.TsvReportPath != "" { + tsvwiter, err := tsv.AppendWriterFile(r.TsvReportPath) + if err != nil { + panic(err) + } + defer tsvwiter.Close() + // Output symbol, total acc. profit, acc. profit 60MA, interval acc. profit, fee, win rate, profit factor + _ = tsvwiter.Write([]string{"#", "Symbol", "accumulatedProfit", "accumulatedProfitMA", fmt.Sprintf("%dd profit", r.AccumulatedDailyProfitWindow), "accumulatedFee", "winRatio", "profitFactor", "60D trades"}) + for i := 0; i <= r.NumberOfInterval-1; i++ { + accumulatedProfit := r.accumulatedProfitPerDay.Index(r.IntervalWindow * i) + accumulatedProfitStr := fmt.Sprintf("%f", accumulatedProfit) + accumulatedProfitMA := r.accumulatedProfitMAPerDay.Index(r.IntervalWindow * i) + accumulatedProfitMAStr := fmt.Sprintf("%f", accumulatedProfitMA) + intervalAccumulatedProfit := r.dailyProfit.Tail(r.AccumulatedDailyProfitWindow+r.IntervalWindow*i).Sum() - r.dailyProfit.Tail(r.IntervalWindow*i).Sum() + intervalAccumulatedProfitStr := fmt.Sprintf("%f", intervalAccumulatedProfit) + accumulatedFee := fmt.Sprintf("%f", r.accumulatedFeePerDay.Index(r.IntervalWindow*i)) + winRatio := fmt.Sprintf("%f", r.winRatioPerDay.Index(r.IntervalWindow*i)) + profitFactor := fmt.Sprintf("%f", r.profitFactorPerDay.Index(r.IntervalWindow*i)) + trades := r.dailyTrades.Tail(60+r.IntervalWindow*i).Sum() - r.dailyTrades.Tail(r.IntervalWindow*i).Sum() + tradesStr := fmt.Sprintf("%f", trades) + + _ = tsvwiter.Write([]string{fmt.Sprintf("%d", i+1), symbol, accumulatedProfitStr, accumulatedProfitMAStr, intervalAccumulatedProfitStr, accumulatedFee, winRatio, profitFactor, tradesStr}) + } + } } func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { @@ -157,27 +244,81 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // initial required information s.session = session + // Set fee rate + if s.session.MakerFeeRate.Sign() > 0 || s.session.TakerFeeRate.Sign() > 0 { + s.Position.SetExchangeFeeRate(s.session.ExchangeName, types.ExchangeFee{ + MakerFeeRate: s.session.MakerFeeRate, + TakerFeeRate: s.session.TakerFeeRate, + }) + } + s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position) s.orderExecutor.BindEnvironment(s.Environment) s.orderExecutor.BindProfitStats(s.ProfitStats) s.orderExecutor.BindTradeStats(s.TradeStats) - // modify := func(p float64) float64 { - // return p - // } - // if s.GraphPNLDeductFee { - // modify = func(p float64) float64 { - // return p * (1. - Fee) - // } - // } - profit := floats.Slice{1., 1.} - price, _ := s.session.LastPrice(s.Symbol) + // AccountValueCalculator + s.AccountValueCalculator = bbgo.NewAccountValueCalculator(s.session, s.Market.QuoteCurrency) + + // Accumulated profit report + if bbgo.IsBackTesting { + if s.AccumulatedProfitReport == nil { + s.AccumulatedProfitReport = &AccumulatedProfitReport{} + } + s.AccumulatedProfitReport.Initialize() + s.orderExecutor.TradeCollector().OnProfit(func(trade types.Trade, profit *types.Profit) { + if profit == nil { + return + } + + s.AccumulatedProfitReport.RecordProfit(profit.Profit) + }) + // s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) { + // s.AccumulatedProfitReport.RecordTrade(trade.Fee) + // }) + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1d, func(kline types.KLine) { + s.AccumulatedProfitReport.DailyUpdate(s.TradeStats) + })) + } + + // For drawing + profitSlice := floats.Slice{1., 1.} + price, _ := session.LastPrice(s.Symbol) initAsset := s.CalcAssetValue(price).Float64() - cumProfit := floats.Slice{initAsset, initAsset} - s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, _profit, netProfit fixedpoint.Value) { - profit.Update(netProfit.Float64()) - cumProfit.Update(s.CalcAssetValue(trade.Price).Float64()) + cumProfitSlice := floats.Slice{initAsset, initAsset} + + s.orderExecutor.TradeCollector().OnTrade(func(trade types.Trade, profit fixedpoint.Value, netProfit fixedpoint.Value) { + if bbgo.IsBackTesting { + s.AccumulatedProfitReport.RecordTrade(trade.Fee) + } + + // For drawing/charting + price := trade.Price.Float64() + if s.buyPrice > 0 { + profitSlice.Update(price / s.buyPrice) + cumProfitSlice.Update(s.CalcAssetValue(trade.Price).Float64()) + } else if s.sellPrice > 0 { + profitSlice.Update(s.sellPrice / price) + cumProfitSlice.Update(s.CalcAssetValue(trade.Price).Float64()) + } + if s.Position.IsDust(trade.Price) { + s.buyPrice = 0 + s.sellPrice = 0 + s.highestPrice = 0 + s.lowestPrice = 0 + } else if s.Position.IsLong() { + s.buyPrice = price + s.sellPrice = 0 + s.highestPrice = s.buyPrice + s.lowestPrice = 0 + } else { + s.sellPrice = price + s.buyPrice = 0 + s.highestPrice = 0 + s.lowestPrice = s.sellPrice + } }) + s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { bbgo.Sync(ctx, s) }) @@ -232,42 +373,20 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se })) - bbgo.RegisterCommand("/draw", "Draw Indicators", func(reply interact.Reply) { - canvas := s.DrawIndicators(s.frameKLine.StartTime) - var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render indicators in oneliner") - reply.Message(fmt.Sprintf("[error] cannot render indicators in drift: %v", err)) - return - } - bbgo.SendPhoto(&buffer) - }) - - bbgo.RegisterCommand("/pnl", "Draw PNL(%) per trade", func(reply interact.Reply) { - canvas := s.DrawPNL(&profit) - var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render pnl in oneliner") - reply.Message(fmt.Sprintf("[error] cannot render pnl in drift: %v", err)) - return - } - bbgo.SendPhoto(&buffer) - }) - - bbgo.RegisterCommand("/cumpnl", "Draw Cumulative PNL(Quote)", func(reply interact.Reply) { - canvas := s.DrawCumPNL(&cumProfit) - var buffer bytes.Buffer - if err := canvas.Render(chart.PNG, &buffer); err != nil { - log.WithError(err).Errorf("cannot render cumpnl in oneliner") - reply.Message(fmt.Sprintf("[error] canot render cumpnl in drift: %v", err)) - return - } - bbgo.SendPhoto(&buffer) - }) - bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() + // Output accumulated profit report + if bbgo.IsBackTesting { + defer s.AccumulatedProfitReport.Output(s.Symbol) + + if s.DrawGraph { + if err := s.Draw(&profitSlice, &cumProfitSlice); err != nil { + log.WithError(err).Errorf("cannot draw graph") + } + } + } + _, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String()) _ = s.orderExecutor.GracefulCancel(ctx) }) @@ -279,136 +398,3 @@ func (s *Strategy) CalcAssetValue(price fixedpoint.Value) fixedpoint.Value { balances := s.session.GetAccount().Balances() return balances[s.Market.BaseCurrency].Total().Mul(price).Add(balances[s.Market.QuoteCurrency].Total()) } - -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, length) - } else { - 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 -} - -func (s *Strategy) DrawCumPNL(cumProfit types.Series) *types.Canvas { - canvas := types.NewCanvas(s.InstanceID()) - canvas.PlotRaw("cumulative 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 -} - -func (s *Strategy) initIndicators(store *bbgo.SerialMarketDataStore) error { - s.ma = &indicator.SMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}} - s.stdevHigh = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}} - s.stdevLow = &indicator.StdDev{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.HLRangeWindow}} - s.alpha = &NRR{ - IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}, - } - s.alpha.SeriesBase.Series = s.alpha - s.atr = &indicator.ATR{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.ATRWindow}} - s.trendLine = &indicator.EWMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.TrendWindow}} - - klines, ok := store.KLinesOfInterval(s.Interval) - klinesLength := len(*klines) - if !ok || klinesLength == 0 { - return errors.New("klines not exists") - } - for _, kline := range *klines { - source := s.GetSource(&kline).Float64() - high := kline.High.Float64() - low := kline.Low.Float64() - s.ma.Update(source) - s.stdevHigh.Update(high - s.ma.Last()) - s.stdevLow.Update(s.ma.Last() - low) - s.alpha.Update(kline.Close.Float64()) - s.trendLine.Update(source) - s.atr.PushK(kline) - s.priceLines.Update(source) - } - if s.frameKLine != nil && klines != nil { - s.frameKLine.Set(&(*klines)[len(*klines)-1]) - } - klines, ok = store.KLinesOfInterval(types.Interval1m) - klinesLength = len(*klines) - if !ok || klinesLength == 0 { - return errors.New("klines not exists") - } - if s.kline1m != nil && klines != nil { - s.kline1m.Set(&(*klines)[len(*klines)-1]) - } - s.startTime = s.kline1m.StartTime.Time().Add(s.kline1m.Interval.Duration()) - return nil -} - -func (s *Strategy) DrawIndicators(time types.Time) *types.Canvas { - canvas := types.NewCanvas(s.InstanceID(), s.Interval) - Length := s.priceLines.Length() - if Length > 300 { - Length = 300 - } - log.Infof("draw indicators with %d data", Length) - mean := s.priceLines.Mean(Length) - highestPrice := s.priceLines.Minus(mean).Abs().Highest(Length) - highestDrift := s.alpha.Abs().Highest(Length) - hi := s.alpha.Abs().Highest(Length) - ratio := highestPrice / highestDrift - - canvas.Plot("alpha", s.alpha.Mul(ratio).Add(mean), time, Length) - canvas.Plot("driftOrig", s.alpha.Mul(highestPrice/hi).Add(mean), time, Length) - canvas.Plot("zero", types.NumberSeries(mean), time, Length) - canvas.Plot("price", s.priceLines, time, Length) - return canvas -} - -func (s *Strategy) Draw(time types.Time, profit types.Series, cumProfit types.Series) { - canvas := s.DrawIndicators(time) - f, err := os.Create(s.CanvasPath) - if err != nil { - log.WithError(err).Errorf("cannot create on %s", s.CanvasPath) - return - } - defer f.Close() - if err := canvas.Render(chart.PNG, f); err != nil { - log.WithError(err).Errorf("cannot render in drift") - } - - canvas = s.DrawPNL(profit) - f, err = os.Create(s.GraphPNLPath) - if err != nil { - log.WithError(err).Errorf("open pnl") - return - } - defer f.Close() - if err := canvas.Render(chart.PNG, f); err != nil { - log.WithError(err).Errorf("render pnl") - } - - canvas = s.DrawCumPNL(cumProfit) - f, err = os.Create(s.GraphCumPNLPath) - if err != nil { - log.WithError(err).Errorf("open cumpnl") - return - } - defer f.Close() - if err := canvas.Render(chart.PNG, f); err != nil { - log.WithError(err).Errorf("render cumpnl") - } -} From 600b17460dc9893ec75c085402fc4f9a9f15bdb9 Mon Sep 17 00:00:00 2001 From: austin362667 Date: Tue, 4 Oct 2022 18:47:14 +0800 Subject: [PATCH 3/3] strategy:irr fix drawing defer close IO issue --- pkg/strategy/irr/draw.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/strategy/irr/draw.go b/pkg/strategy/irr/draw.go index ffa525bfa..cf9f98bfa 100644 --- a/pkg/strategy/irr/draw.go +++ b/pkg/strategy/irr/draw.go @@ -37,21 +37,21 @@ func (s *Strategy) InitDrawCommands(profit, cumProfit types.Series) { func (s *Strategy) Draw(profit, cumProfit types.Series) error { canvas := DrawPNL(s.InstanceID(), profit) - f, err := os.Create(s.GraphPNLPath) + fPnL, err := os.Create(s.GraphPNLPath) if err != nil { return fmt.Errorf("cannot create on path " + s.GraphPNLPath) } - defer f.Close() - if err = canvas.Render(chart.PNG, f); err != nil { + defer fPnL.Close() + if err = canvas.Render(chart.PNG, fPnL); err != nil { return fmt.Errorf("cannot render pnl") } canvas = DrawCumPNL(s.InstanceID(), cumProfit) - f, err = os.Create(s.GraphCumPNLPath) + fCumPnL, err := os.Create(s.GraphCumPNLPath) if err != nil { return fmt.Errorf("cannot create on path " + s.GraphCumPNLPath) } - defer f.Close() - if err = canvas.Render(chart.PNG, f); err != nil { + defer fCumPnL.Close() + if err = canvas.Render(chart.PNG, fCumPnL); err != nil { return fmt.Errorf("cannot render cumpnl") }