diff --git a/backtesting.go b/backtesting.go index c2774d6..e7798bf 100644 --- a/backtesting.go +++ b/backtesting.go @@ -33,11 +33,12 @@ func Backtest(trader *Trader) { chart := charts.NewLine() chart.SetGlobalOptions(charts.WithTitleOpts(opts.Title{ - Title: "Backtest", + Title: fmt.Sprintf("Backtest (%s)", time.Now().Format(time.DateTime)), Subtitle: fmt.Sprintf("%s %s %T", trader.Symbol, trader.Frequency, trader.Strategy), })) chart.SetXAxis(seriesStringArray(trader.Stats().Dates())). - AddSeries("Equity", lineDataFromSeries(trader.Stats().Series("Equity"))) + AddSeries("Equity", lineDataFromSeries(trader.Stats().Series("Equity"))). + AddSeries("Drawdown", lineDataFromSeries(trader.Stats().Series("Drawdown"))) // Draw the chart to a file. f, err := os.Create("backtest.html") @@ -65,11 +66,27 @@ func lineDataFromSeries(s Series) []opts.LineData { } func seriesStringArray(s Series) []string { + if s == nil || s.Len() == 0 { + return []string{} + } + first := true data := make([]string, s.Len()) + var dateLayout string for i := 0; i < s.Len(); i++ { switch val := s.Value(i).(type) { case time.Time: - data[i] = val.Format(time.DateTime) + if first { + first = false + dateHead := s.Value(0).(time.Time) + dateTail := s.Value(-1).(time.Time) + diff := dateTail.Sub(dateHead) + if diff.Hours() > 24*365 { + dateLayout = time.DateOnly + } else { + dateLayout = time.DateTime + } + } + data[i] = val.Format(dateLayout) case string: data[i] = fmt.Sprintf("%q", val) default: diff --git a/trader.go b/trader.go index 1bb901b..6c4053e 100644 --- a/trader.go +++ b/trader.go @@ -82,6 +82,7 @@ func (t *Trader) Init() { t.stats = NewDataFrame( NewDataSeries(dataframe.NewSeriesTime("Date", nil)), NewDataSeries(dataframe.NewSeriesFloat64("Equity", nil)), + NewDataSeries(dataframe.NewSeriesFloat64("Drawdown", nil)), ) } @@ -95,6 +96,15 @@ func (t *Trader) Tick() { t.stats.PushValues(map[string]interface{}{ "Date": t.data.Date(-1), "Equity": t.Broker.NAV(), + "Drawdown": func() float64 { + var bal float64 + if t.stats.Len() > 0 { + bal = t.stats.Value("Equity", 0).(float64) // Take starting balance + } else { + bal = t.Broker.NAV() + } + return Max(bal-t.Broker.NAV(), 0) + }(), }) }