diff --git a/cmd/indicator-backtest/main.go b/cmd/indicator-backtest/main.go index 8bb99bd..9837c7e 100644 --- a/cmd/indicator-backtest/main.go +++ b/cmd/indicator-backtest/main.go @@ -12,6 +12,7 @@ import ( "os" "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" "github.com/cinar/indicator/v2/strategy" "github.com/cinar/indicator/v2/strategy/compound" "github.com/cinar/indicator/v2/strategy/momentum" @@ -27,6 +28,7 @@ func main() { var writeStrategyRerpots bool var addSplits bool var addAnds bool + var dateFormat string fmt.Fprintln(os.Stderr, "Indicator Backtest") fmt.Fprintln(os.Stderr, "Copyright (c) 2021-2024 Onur Cinar.") @@ -41,6 +43,7 @@ func main() { flag.BoolVar(&writeStrategyRerpots, "write-strategy-reports", strategy.DefaultWriteStrategyReports, "write individual strategy reports") flag.BoolVar(&addSplits, "splits", false, "add the split strategies") flag.BoolVar(&addAnds, "ands", false, "add the and strategies") + flag.StringVar(&dateFormat, "date-format", helper.DefaultReportDateFormat, "date format to use") flag.Parse() flag.Parse() @@ -51,6 +54,7 @@ func main() { backtest.Workers = workers backtest.LastDays = lastDays backtest.WriteStrategyReports = writeStrategyRerpots + backtest.DateFormat = dateFormat backtest.Names = append(backtest.Names, flag.Args()...) backtest.Strategies = append(backtest.Strategies, compound.AllStrategies()...) backtest.Strategies = append(backtest.Strategies, momentum.AllStrategies()...) diff --git a/helper/report.go b/helper/report.go index 2a4fe92..aca307b 100644 --- a/helper/report.go +++ b/helper/report.go @@ -17,6 +17,11 @@ import ( //go:embed "report.tmpl" var reportTmpl string +const ( + // DefaultReportDateFormat is the default date format used in the report. + DefaultReportDateFormat = "2006-01-02" +) + // ReportColumn defines the interface that all report data columns must implement. // This interface ensures that different types of data columns can be used // consistently within the report generation process. @@ -34,16 +39,6 @@ type ReportColumn interface { Value() string } -// reportModel struct holds the data that is exposed to the template renderer -// for generating the report. It encapsulates all the information necessary -// to render the report's content, including data, and annotations. -type reportModel struct { - Title string - Date <-chan time.Time - Columns []ReportColumn - Views [][]int -} - // Report generates an HTML file containing an interactive chart that // visually represents the provided data and annotations. // @@ -51,7 +46,11 @@ type reportModel struct { // the data visually, interact with the chart elements, and view // the associated annotations. type Report struct { - model reportModel + Title string + Date <-chan time.Time + Columns []ReportColumn + Views [][]int + DateFormat string } // NewReport takes a channel of time as the time axis and returns a new @@ -59,14 +58,13 @@ type Report struct { // add data and annotations and subsequently generate a report. func NewReport(title string, date <-chan time.Time) *Report { return &Report{ - model: reportModel{ - Title: title, - Date: date, - Columns: []ReportColumn{}, - Views: [][]int{ - {}, - }, + Title: title, + Date: date, + Columns: []ReportColumn{}, + Views: [][]int{ + {}, }, + DateFormat: DefaultReportDateFormat, } } @@ -74,22 +72,22 @@ func NewReport(title string, date <-chan time.Time) *Report { // identifier. This identifier can be used later to refer to the // chart and add columns to it. func (r *Report) AddChart() int { - r.model.Views = append(r.model.Views, []int{}) - return len(r.model.Views) - 1 + r.Views = append(r.Views, []int{}) + return len(r.Views) - 1 } // AddColumn adds a new data column to the specified charts. If no // chart is specified, it will be added to the main chart. func (r *Report) AddColumn(column ReportColumn, charts ...int) { - r.model.Columns = append(r.model.Columns, column) - columnID := len(r.model.Columns) + r.Columns = append(r.Columns, column) + columnID := len(r.Columns) if len(charts) == 0 { charts = append(charts, 0) } for _, chartID := range charts { - r.model.Views[chartID] = append(r.model.Views[chartID], columnID) + r.Views[chartID] = append(r.Views[chartID], columnID) } } @@ -102,7 +100,7 @@ func (r *Report) WriteToWriter(writer io.Writer) error { return err } - return tmpl.Execute(writer, r.model) + return tmpl.Execute(writer, r) } // WriteToFile writes the generated report content to a file with diff --git a/helper/report.tmpl b/helper/report.tmpl index d1696fd..17ba99e 100644 --- a/helper/report.tmpl +++ b/helper/report.tmpl @@ -102,7 +102,7 @@ {{ range .Date }} data.addRow([ - new Date("{{ .Format "2006-01-02" }}"), + new Date("{{ .Format $.DateFormat }}"), {{ range $.Columns }} {{ .Value }}, {{ end }} @@ -119,4 +119,4 @@ - \ No newline at end of file + diff --git a/strategy/backtest.go b/strategy/backtest.go index 7758efb..16a5b91 100644 --- a/strategy/backtest.go +++ b/strategy/backtest.go @@ -62,6 +62,9 @@ type Backtest struct { // WriteStrategyReports indicates whether the individual strategy reports should be generated. WriteStrategyReports bool + + // DateFormat is the date format that is used in the reports. + DateFormat string } // backtestResult encapsulates the outcome of running a strategy. @@ -95,6 +98,7 @@ func NewBacktest(repository asset.Repository, outputDir string) *Backtest { Workers: DefaultBacktestWorkers, LastDays: DefaultLastDays, WriteStrategyReports: DefaultWriteStrategyReports, + DateFormat: helper.DefaultReportDateFormat, } } @@ -204,6 +208,7 @@ func (b *Backtest) worker(names <-chan string, bestResults chan<- *backtestResul // Generate inidividual strategy report. if b.WriteStrategyReports { report := st.Report(helper.SliceToChan(snapshotsSlice)) + report.DateFormat = b.DateFormat err := report.WriteToFile(path.Join(b.outputDir, b.strategyReportFileName(name, st.Name()))) if err != nil {