From 0d7990fc18c51ffa0b8d30e2b9716bc31af7adb4 Mon Sep 17 00:00:00 2001 From: Sven Woldt Date: Thu, 2 Nov 2023 18:03:16 +0100 Subject: [PATCH] reset main --- pkg/backtest/report.go | 58 ++++---------- pkg/cmd/backtest.go | 104 ++++++------------------ pkg/datatype/floats/slice.go | 12 --- pkg/types/trade_stat.go | 151 ----------------------------------- pkg/types/trade_stat_test.go | 56 ------------- pkg/types/trade_stats.go | 102 ++++++++++------------- 6 files changed, 78 insertions(+), 405 deletions(-) delete mode 100644 pkg/types/trade_stat.go delete mode 100644 pkg/types/trade_stat_test.go diff --git a/pkg/backtest/report.go b/pkg/backtest/report.go index 3772e2786..bce827699 100644 --- a/pkg/backtest/report.go +++ b/pkg/backtest/report.go @@ -68,49 +68,21 @@ func ReadSummaryReport(filename string) (*SummaryReport, error) { // SessionSymbolReport is the report per exchange session // trades are merged, collected and re-calculated type SessionSymbolReport struct { - Exchange types.ExchangeName `json:"exchange"` - Symbol string `json:"symbol,omitempty"` - Intervals []types.Interval `json:"intervals,omitempty"` - Subscriptions []types.Subscription `json:"subscriptions"` - Market types.Market `json:"market"` - LastPrice fixedpoint.Value `json:"lastPrice,omitempty"` - StartPrice fixedpoint.Value `json:"startPrice,omitempty"` - PnL *pnl.AverageCostPnLReport `json:"pnl,omitempty"` - InitialBalances types.BalanceMap `json:"initialBalances,omitempty"` - FinalBalances types.BalanceMap `json:"finalBalances,omitempty"` - Manifests Manifests `json:"manifests,omitempty"` - TradeCount fixedpoint.Value `json:"tradeCount,omitempty"` - RoundTurnCount fixedpoint.Value `json:"roundTurnCount,omitempty"` - TotalNetProfit fixedpoint.Value `json:"totalNetProfit,omitempty"` - AvgNetProfit fixedpoint.Value `json:"avgNetProfit,omitempty"` - GrossProfit fixedpoint.Value `json:"grossProfit,omitempty"` - GrossLoss fixedpoint.Value `json:"grossLoss,omitempty"` - PRR fixedpoint.Value `json:"prr,omitempty"` - PercentProfitable fixedpoint.Value `json:"percentProfitable,omitempty"` - MaxDrawdown fixedpoint.Value `json:"maxDrawdown,omitempty"` - AverageDrawdown fixedpoint.Value `json:"avgDrawdown,omitempty"` - MaxProfit fixedpoint.Value `json:"maxProfit,omitempty"` - MaxLoss fixedpoint.Value `json:"maxLoss,omitempty"` - AvgProfit fixedpoint.Value `json:"avgProfit,omitempty"` - AvgLoss fixedpoint.Value `json:"avgLoss,omitempty"` - TotalTimeInMarketSec int64 `json:"totalTimeInMarketSec,omitempty"` - AvgHoldSec int64 `json:"avgHoldSec,omitempty"` - WinningCount int `json:"winningCount,omitempty"` - LosingCount int `json:"losingCount,omitempty"` - MaxLossStreak int `json:"maxLossStreak,omitempty"` - Sharpe fixedpoint.Value `json:"sharpeRatio"` - AnnualHistoricVolatility fixedpoint.Value `json:"annualHistoricVolatility,omitempty"` - CAGR fixedpoint.Value `json:"cagr,omitempty"` - Calmar fixedpoint.Value `json:"calmar,omitempty"` - Sterling fixedpoint.Value `json:"sterling,omitempty"` - Burke fixedpoint.Value `json:"burke,omitempty"` - Kelly fixedpoint.Value `json:"kelly,omitempty"` - OptimalF fixedpoint.Value `json:"optimalF,omitempty"` - StatN fixedpoint.Value `json:"statN,omitempty"` - StdErr fixedpoint.Value `json:"statNStdErr,omitempty"` - Sortino fixedpoint.Value `json:"sortinoRatio"` - ProfitFactor fixedpoint.Value `json:"profitFactor"` - WinningRatio fixedpoint.Value `json:"winningRatio"` + Exchange types.ExchangeName `json:"exchange"` + Symbol string `json:"symbol,omitempty"` + Intervals []types.Interval `json:"intervals,omitempty"` + Subscriptions []types.Subscription `json:"subscriptions"` + Market types.Market `json:"market"` + LastPrice fixedpoint.Value `json:"lastPrice,omitempty"` + StartPrice fixedpoint.Value `json:"startPrice,omitempty"` + PnL *pnl.AverageCostPnLReport `json:"pnl,omitempty"` + InitialBalances types.BalanceMap `json:"initialBalances,omitempty"` + FinalBalances types.BalanceMap `json:"finalBalances,omitempty"` + Manifests Manifests `json:"manifests,omitempty"` + Sharpe fixedpoint.Value `json:"sharpeRatio"` + Sortino fixedpoint.Value `json:"sortinoRatio"` + ProfitFactor fixedpoint.Value `json:"profitFactor"` + WinningRatio fixedpoint.Value `json:"winningRatio"` } func (r *SessionSymbolReport) InitialEquityValue() fixedpoint.Value { diff --git a/pkg/cmd/backtest.go b/pkg/cmd/backtest.go index 44edaa5c7..e5dc12e52 100644 --- a/pkg/cmd/backtest.go +++ b/pkg/cmd/backtest.go @@ -535,7 +535,8 @@ var BacktestCmd = &cobra.Command{ profitFactor := tradeState.ProfitFactor winningRatio := tradeState.WinningRatio intervalProfits := tradeState.IntervalProfits[types.Interval1d] - symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Copy(), tradeStats) + + symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Copy(), intervalProfits, profitFactor, winningRatio) if err != nil { return err } @@ -600,12 +601,14 @@ var BacktestCmd = &cobra.Command{ }, } -func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade, tradeStats *types.TradeStats) ( +func createSymbolReport( + userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade, + intervalProfit *types.IntervalProfitCollector, + profitFactor, winningRatio fixedpoint.Value, +) ( *backtest.SessionSymbolReport, error, ) { - intervalProfit := tradeStats.IntervalProfits[types.Interval1d] - backtestExchange, ok := session.Exchange.(*backtest.Exchange) if !ok { return nil, fmt.Errorf("unexpected error, exchange instance is not a backtest exchange") @@ -615,11 +618,6 @@ func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, if !ok { return nil, fmt.Errorf("market not found: %s, %s", symbol, session.Exchange.Name()) } - tStart, tEnd := trades[0].Time, trades[len(trades)-1].Time - - periodStart := tStart.Time() - periodEnd := tEnd.Time() - period := periodEnd.Sub(periodStart) startPrice, ok := session.StartPrice(symbol) if !ok { @@ -636,81 +634,29 @@ func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, Market: market, } + sharpeRatio := fixedpoint.NewFromFloat(intervalProfit.GetSharpe()) + sortinoRatio := fixedpoint.NewFromFloat(intervalProfit.GetSortino()) + report := calculator.Calculate(symbol, trades, lastPrice) accountConfig := userConfig.Backtest.GetAccount(session.Exchange.Name().String()) initBalances := accountConfig.Balances.BalanceMap() finalBalances := session.GetAccount().Balances() - maxProfit := n(intervalProfit.Profits.Max()) - maxLoss := n(intervalProfit.Profits.Min()) - drawdown := types.Drawdown(intervalProfit.Profits) - maxDrawdown := drawdown.Max() - avgDrawdown := drawdown.Average() - roundTurnCount := n(float64(tradeStats.NumOfProfitTrade + tradeStats.NumOfLossTrade)) - roundTurnLength := n(float64(intervalProfit.Profits.Length())) - winningCount := n(float64(tradeStats.NumOfProfitTrade)) - loosingCount := n(float64(tradeStats.NumOfLossTrade)) - avgProfit := tradeStats.GrossProfit.Div(n(types.NNZ(float64(tradeStats.NumOfProfitTrade), 1))) - avgLoss := tradeStats.GrossLoss.Div(n(types.NNZ(float64(tradeStats.NumOfLossTrade), 1))) - - winningPct := winningCount.Div(roundTurnCount) - // losingPct := fixedpoint.One.Sub(winningPct) - - sharpeRatio := n(intervalProfit.GetSharpe()) - sortinoRatio := n(intervalProfit.GetSortino()) - annVolHis := n(types.AnnualHistoricVolatility(intervalProfit.Profits)) - totalTimeInMarketSec, avgHoldSec := intervalProfit.GetTimeInMarket() - statn, stdErr := types.StatN(intervalProfit.Profits) symbolReport := backtest.SessionSymbolReport{ - Exchange: session.Exchange.Name(), - Symbol: symbol, - Market: market, - LastPrice: lastPrice, - StartPrice: startPrice, - InitialBalances: initBalances, - FinalBalances: finalBalances, - TradeCount: fixedpoint.NewFromInt(int64(len(trades))), - GrossLoss: tradeStats.GrossLoss, - GrossProfit: tradeStats.GrossProfit, - WinningCount: tradeStats.NumOfProfitTrade, - LosingCount: tradeStats.NumOfLossTrade, - RoundTurnCount: roundTurnCount, - WinningRatio: tradeStats.WinningRatio, - PercentProfitable: winningPct, - ProfitFactor: tradeStats.ProfitFactor, - MaxDrawdown: n(maxDrawdown), - AverageDrawdown: n(avgDrawdown), - MaxProfit: maxProfit, - MaxLoss: maxLoss, - MaxLossStreak: tradeStats.MaximumConsecutiveLosses, - TotalTimeInMarketSec: totalTimeInMarketSec, - AvgHoldSec: avgHoldSec, - AvgProfit: avgProfit, - AvgLoss: avgLoss, - AvgNetProfit: tradeStats.TotalNetProfit.Div(roundTurnLength), - TotalNetProfit: tradeStats.TotalNetProfit, - AnnualHistoricVolatility: annVolHis, - PnL: report, - PRR: types.PRR(tradeStats.GrossProfit, tradeStats.GrossLoss, winningCount, loosingCount), - Kelly: types.KellyCriterion(tradeStats.ProfitFactor, winningPct), - OptimalF: types.OptimalF(intervalProfit.Profits), - StatN: statn, - StdErr: stdErr, - Sharpe: sharpeRatio, - Sortino: sortinoRatio, + Exchange: session.Exchange.Name(), + Symbol: symbol, + Market: market, + LastPrice: lastPrice, + StartPrice: startPrice, + PnL: report, + InitialBalances: initBalances, + FinalBalances: finalBalances, + // Manifests: manifests, + Sharpe: sharpeRatio, + Sortino: sortinoRatio, + ProfitFactor: profitFactor, + WinningRatio: winningRatio, } - cagr := types.NN( - types.CAGR( - symbolReport.InitialEquityValue().Float64(), - symbolReport.FinalEquityValue().Float64(), - int(period.Hours())/24, - ), 0) - - symbolReport.CAGR = n(cagr) - symbolReport.Calmar = n(types.CalmarRatio(cagr, maxDrawdown)) - symbolReport.Sterling = n(types.SterlingRatio(cagr, avgDrawdown)) - symbolReport.Burke = n(types.BurkeRatio(cagr, drawdown.AverageSquared())) - for _, s := range session.Subscriptions { symbolReport.Subscriptions = append(symbolReport.Subscriptions, s) } @@ -729,10 +675,6 @@ func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, return &symbolReport, nil } -func n(v float64) fixedpoint.Value { - return fixedpoint.NewFromFloat(v) -} - func verify( userConfig *bbgo.Config, backtestService *service.BacktestService, sourceExchanges map[types.ExchangeName]types.Exchange, startTime, endTime time.Time, diff --git a/pkg/datatype/floats/slice.go b/pkg/datatype/floats/slice.go index dd7b14aa1..1d610a4f5 100644 --- a/pkg/datatype/floats/slice.go +++ b/pkg/datatype/floats/slice.go @@ -112,18 +112,6 @@ func (s Slice) Average() float64 { return total / float64(len(s)) } -func (s Slice) AverageSquared() float64 { - if len(s) == 0 { - return 0.0 - } - - total := 0.0 - for _, value := range s { - total += math.Pow(value, 2) - } - return total / float64(len(s)) -} - func (s Slice) Diff() (values Slice) { for i, v := range s { if i == 0 { diff --git a/pkg/types/trade_stat.go b/pkg/types/trade_stat.go deleted file mode 100644 index d89c59484..000000000 --- a/pkg/types/trade_stat.go +++ /dev/null @@ -1,151 +0,0 @@ -package types - -import ( - "math" - - "gonum.org/v1/gonum/stat" - - "github.com/c9s/bbgo/pkg/datatype/floats" - "github.com/c9s/bbgo/pkg/fixedpoint" -) - -const ( - // DailyToAnnualFactor is the factor to scale daily observations to annual. - // Commonly defined as the number of public market trading days in a year. - DailyToAnnualFactor = 252 // todo does this apply to crypto at all? -) - -// AnnualHistoricVolatility is the historic volatility of the equity curve as annualized std dev. -func AnnualHistoricVolatility(data Series) float64 { - var sd = Stdev(data, data.Length(), 1) - return sd * math.Sqrt(DailyToAnnualFactor) -} - -// CAGR is the Compound Annual Growth Rate of the equity curve. -func CAGR(initial, final float64, days int) float64 { - var ( - growthRate = (final - initial) / initial - x = 1 + growthRate - y = 365.0 / float64(days) - ) - return math.Pow(x, y) - 1 -} - -// measures of risk-adjusted return based on drawdown risk - -// calmar ratio - discounts expected excess return of a portfolio by the -// worst expected maximum draw down for that portfolio -// CR = E(re)/MD1 = (E(r) - rf) / MD1 -func CalmarRatio(cagr, maxDrawdown float64) float64 { - return cagr / maxDrawdown -} - -// Sterling ratio -// discounts the expected excess return of a portfolio by the average of the N worst -// expected maximum drawdowns for that portfolio -// CR = E(re) / (1/N)(sum MDi) -func SterlingRatio(cagr, avgDrawdown float64) float64 { - return cagr / avgDrawdown -} - -// Burke Ratio -// similar to sterling, but less sensitive to outliers -// discounts the expected excess return of a portfolio by the square root of the average -// of the N worst expected maximum drawdowns for that portfolio -// BR = E(re) / ((1/N)(sum MD^2))^0.5 ---> smoothing, can take roots, logs etc -func BurkeRatio(cagr, avgDrawdownSquared float64) float64 { - return cagr / math.Sqrt(avgDrawdownSquared) -} - -// KellyCriterion the famous method for trade sizing. -func KellyCriterion(profitFactor, winP fixedpoint.Value) fixedpoint.Value { - return profitFactor.Mul(winP).Sub(fixedpoint.One.Sub(winP)).Div(profitFactor) -} - -// PRR (Pessimistic Return Ratio) is the profit factor with a penalty for a lower number of roundturns. -func PRR(profit, loss, winningN, losingN fixedpoint.Value) fixedpoint.Value { - var ( - winF = 1 / math.Sqrt(1+winningN.Float64()) - loseF = 1 / math.Sqrt(1+losingN.Float64()) - ) - return fixedpoint.NewFromFloat((1 - winF) / (1 + loseF) * (1 + profit.Float64()) / (1 + loss.Float64())) -} - -// StatN returns the statistically significant number of samples required based on the distribution of a series. -// From: https://www.elitetrader.com/et/threads/minimum-number-of-roundturns-required-for-backtesting-results-to-be-trusted.356588/page-2 -func StatN(xs floats.Slice) (sn, se fixedpoint.Value) { - var ( - sd = Stdev(xs, xs.Length(), 1) - m = Mean(xs) - statn = math.Pow(4*(sd/m), 2) - stdErr = stat.StdErr(sd, float64(xs.Length())) - ) - return fixedpoint.NewFromFloat(statn), fixedpoint.NewFromFloat(stdErr) -} - -// OptimalF is a function that returns the 'OptimalF' for a series of trade returns as defined by Ralph Vince. -// It is a method for sizing positions to maximize geometric return whilst accounting for biggest trading loss. -// See: https://www.investopedia.com/terms/o/optimalf.asp -// Param roundturns is the series of profits (-ve amount for losses) for each trade -func OptimalF(roundturns floats.Slice) fixedpoint.Value { - var ( - maxTWR, optimalF float64 - maxLoss = roundturns.Min() - ) - for i := 1.0; i <= 100.0; i++ { - twr := 1.0 - f := i / 100 - for j := range roundturns { - if roundturns[j] == 0 { - continue - } - hpr := 1 + f*(-roundturns[j]/maxLoss) - twr *= hpr - } - if twr > maxTWR { - maxTWR = twr - optimalF = f - } - } - - return fixedpoint.NewFromFloat(optimalF) -} - -// NN (Not Number) returns y if x is NaN or Inf. -func NN(x, y float64) float64 { - if math.IsNaN(x) || math.IsInf(x, 0) { - return y - } - return x -} - -// NNZ (Not Number or Zero) returns y if x is NaN or Inf or Zero. -func NNZ(x, y float64) float64 { - if NN(x, y) == y || x == 0 { - return y - } - return x -} - -// Compute the drawdown function associated to a portfolio equity curve, -// also called the portfolio underwater equity curve. -// Portfolio Optimization with Drawdown Constraints, Chekhlov et al., 2000 -// http://papers.ssrn.com/sol3/papers.cfm?abstract_id=223323 -func Drawdown(equityCurve floats.Slice) floats.Slice { - // Initialize highWaterMark - highWaterMark := math.Inf(-1) - - // Create ddVector with the same length as equityCurve - ddVector := make([]float64, len(equityCurve)) - - // Loop over all the values to compute the drawdown vector - for i := 0; i < len(equityCurve); i++ { - if equityCurve[i] > highWaterMark { - highWaterMark = equityCurve[i] - } - - ddVector[i] = (highWaterMark - equityCurve[i]) / highWaterMark - } - - return ddVector -} diff --git a/pkg/types/trade_stat_test.go b/pkg/types/trade_stat_test.go deleted file mode 100644 index 4236d0b25..000000000 --- a/pkg/types/trade_stat_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/c9s/bbgo/pkg/datatype/floats" - "github.com/c9s/bbgo/pkg/fixedpoint" -) - -func TestCAGR(t *testing.T) { - giveInitial := 1000.0 - giveFinal := 2500.0 - giveDays := 190 - want := 4.81 - act := CAGR(giveInitial, giveFinal, giveDays) - assert.InDelta(t, want, act, 0.01) -} - -func TestKellyCriterion(t *testing.T) { - var ( - giveProfitFactor = fixedpoint.NewFromFloat(1.6) - giveWinP = fixedpoint.NewFromFloat(0.7) - want = 0.51 - act = KellyCriterion(giveProfitFactor, giveWinP) - ) - assert.InDelta(t, want, act.Float64(), 0.01) -} - -func TestAnnualHistoricVolatility(t *testing.T) { - var ( - give = floats.Slice{0.1, 0.2, -0.15, 0.1, 0.8, -0.3, 0.2} - want = 5.51 - act = AnnualHistoricVolatility(give) - ) - assert.InDelta(t, want, act, 0.01) -} - -func TestOptimalF(t *testing.T) { - roundturns := floats.Slice{10, 20, 50, -10, 40, -40} - f := OptimalF(roundturns) - assert.EqualValues(t, 0.45, f) -} - -func TestDrawdown(t *testing.T) { - roundturns := floats.Slice{100, 50, 100} - expected := []float64{.0, .5, .0} - drawdown := Drawdown(roundturns) - assert.EqualValues(t, 0.5, drawdown.Max()) - assert.EqualValues(t, 0.16666666666666666, drawdown.Average()) - assert.EqualValues(t, 0.08333333333333333, drawdown.AverageSquared()) - for i, v := range expected { - assert.EqualValues(t, v, drawdown[i]) - } -} diff --git a/pkg/types/trade_stats.go b/pkg/types/trade_stats.go index 0cc3e0f48..a7cfa2e98 100644 --- a/pkg/types/trade_stats.go +++ b/pkg/types/trade_stats.go @@ -14,10 +14,38 @@ import ( "github.com/c9s/bbgo/pkg/fixedpoint" ) -const ( - ErrStartTimeNotValid = "No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?" - ErrProfitArrEmpty = "profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?" -) +type IntervalProfitCollector struct { + Interval Interval `json:"interval"` + Profits *floats.Slice `json:"profits"` + Timestamp *floats.Slice `json:"timestamp"` + tmpTime time.Time `json:"tmpTime"` +} + +func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector { + return &IntervalProfitCollector{Interval: i, tmpTime: startTime, Profits: &floats.Slice{1.}, Timestamp: &floats.Slice{float64(startTime.Unix())}} +} + +// Update the collector by every traded profit +func (s *IntervalProfitCollector) Update(profit *Profit) { + if s.tmpTime.IsZero() { + panic("No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") + } else { + duration := s.Interval.Duration() + if profit.TradedAt.Before(s.tmpTime.Add(duration)) { + (*s.Profits)[len(*s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64() + } else { + 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 + } + } + } + } +} type ProfitReport struct { StartTime time.Time `json:"startTime"` @@ -33,55 +61,6 @@ func (s ProfitReport) String() string { return string(b) } -type IntervalProfitCollector struct { - Interval Interval `json:"interval"` - Profits floats.Slice `json:"profits"` - TimeInMarket []time.Duration `json:"timeInMarket"` - Timestamp floats.Slice `json:"timestamp"` - tmpTime time.Time `json:"tmpTime"` -} - -func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector { - return &IntervalProfitCollector{Interval: i, tmpTime: startTime, Profits: floats.Slice{1.}, Timestamp: floats.Slice{float64(startTime.Unix())}} -} - -// Update the collector by every traded profit -func (s *IntervalProfitCollector) Update(profit *Profit) { - if s.tmpTime.IsZero() { - panic(ErrStartTimeNotValid) - } else { - s.TimeInMarket = append(s.TimeInMarket, profit.TradedAt.Sub(profit.PositionOpenedAt)) - duration := s.Interval.Duration() - if profit.TradedAt.Before(s.tmpTime.Add(duration)) { - (s.Profits)[len(s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64() - } else { - 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 - } - } - } - } -} - -// Determine average and total time spend in market -func (s *IntervalProfitCollector) GetTimeInMarket() (avgHoldSec, totalTimeInMarketSec int64) { - if s.Profits == nil { - return 0, 0 - } - l := len(s.TimeInMarket) - for i := 0; i < l; i++ { - d := s.TimeInMarket[i] - totalTimeInMarketSec += int64(d / time.Millisecond) - } - avgHoldSec = totalTimeInMarketSec / int64(l) - return -} - // Get all none-profitable intervals func (s *IntervalProfitCollector) GetNonProfitableIntervals() (result []ProfitReport) { if s.Profits == nil { @@ -113,9 +92,9 @@ func (s *IntervalProfitCollector) GetProfitableIntervals() (result []ProfitRepor // Get number of profitable traded intervals func (s *IntervalProfitCollector) GetNumOfProfitableIntervals() (profit int) { if s.Profits == nil { - panic(ErrProfitArrEmpty) + panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") } - for _, v := range s.Profits { + for _, v := range *s.Profits { if v > 1. { profit += 1 } @@ -127,9 +106,9 @@ func (s *IntervalProfitCollector) GetNumOfProfitableIntervals() (profit int) { // (no trade within the interval or pnl = 0 will be also included here) func (s *IntervalProfitCollector) GetNumOfNonProfitableIntervals() (nonprofit int) { if s.Profits == nil { - panic(ErrProfitArrEmpty) + panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") } - for _, v := range s.Profits { + for _, v := range *s.Profits { if v <= 1. { nonprofit += 1 } @@ -141,11 +120,10 @@ func (s *IntervalProfitCollector) GetNumOfNonProfitableIntervals() (nonprofit in // no smart sharpe ON for the calculated result func (s *IntervalProfitCollector) GetSharpe() float64 { if s.tmpTime.IsZero() { - panic(ErrStartTimeNotValid) + panic("No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") } if s.Profits == nil { - panic(ErrStartTimeNotValid) - + panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") } return Sharpe(Sub(s.Profits, 1.), s.Profits.Length(), true, false) } @@ -154,10 +132,10 @@ func (s *IntervalProfitCollector) GetSharpe() float64 { // No risk-free return rate and smart sortino OFF for the calculated result. func (s *IntervalProfitCollector) GetSortino() float64 { if s.tmpTime.IsZero() { - panic(ErrStartTimeNotValid) + panic("No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") } if s.Profits == nil { - panic(ErrProfitArrEmpty) + panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?") } return Sortino(Sub(s.Profits, 1.), 0., s.Profits.Length(), true, false) }