diff --git a/backtest/README.md b/backtest/README.md index fffa95a..b25002d 100644 --- a/backtest/README.md +++ b/backtest/README.md @@ -25,9 +25,18 @@ The information provided on this project is strictly for informational purposes ## Index - [Constants](<#constants>) +- [func RegisterReportBuilder\(name string, builder ReportBuilderFunc\)](<#RegisterReportBuilder>) - [type Backtest](<#Backtest>) - [func NewBacktest\(repository asset.Repository, report Report\) \*Backtest](<#NewBacktest>) - [func \(b \*Backtest\) Run\(\) error](<#Backtest.Run>) +- [type DataReport](<#DataReport>) + - [func NewDataReport\(\) \*DataReport](<#NewDataReport>) + - [func \(d \*DataReport\) AssetBegin\(name string, strategies \[\]strategy.Strategy\) error](<#DataReport.AssetBegin>) + - [func \(\*DataReport\) AssetEnd\(\_ string\) error](<#DataReport.AssetEnd>) + - [func \(\*DataReport\) Begin\(\_ \[\]string, \_ \[\]strategy.Strategy\) error](<#DataReport.Begin>) + - [func \(\*DataReport\) End\(\) error](<#DataReport.End>) + - [func \(d \*DataReport\) Write\(assetName string, currentStrategy strategy.Strategy, snapshots \<\-chan \*asset.Snapshot, actions \<\-chan strategy.Action, outcomes \<\-chan float64\) error](<#DataReport.Write>) +- [type DataStrategyResult](<#DataStrategyResult>) - [type HTMLReport](<#HTMLReport>) - [func NewHTMLReport\(outputDir string\) \*HTMLReport](<#NewHTMLReport>) - [func \(h \*HTMLReport\) AssetBegin\(name string, strategies \[\]strategy.Strategy\) error](<#HTMLReport.AssetBegin>) @@ -36,6 +45,8 @@ The information provided on this project is strictly for informational purposes - [func \(h \*HTMLReport\) End\(\) error](<#HTMLReport.End>) - [func \(h \*HTMLReport\) Write\(assetName string, currentStrategy strategy.Strategy, snapshots \<\-chan \*asset.Snapshot, actions \<\-chan strategy.Action, outcomes \<\-chan float64\) error](<#HTMLReport.Write>) - [type Report](<#Report>) + - [func NewReport\(name, config string\) \(Report, error\)](<#NewReport>) +- [type ReportBuilderFunc](<#ReportBuilderFunc>) ## Constants @@ -61,6 +72,24 @@ const ( ) ``` + + +```go +const ( + // HTMLReportBuilderName is the name for the HTML report builder. + HTMLReportBuilderName = "html" +) +``` + + +## func [RegisterReportBuilder]() + +```go +func RegisterReportBuilder(name string, builder ReportBuilderFunc) +``` + +RegisterReportBuilder registers the given builder. + ## type [Backtest]() @@ -102,10 +131,100 @@ func (b *Backtest) Run() error Run executes a comprehensive performance evaluation of the designated strategies, applied to a specified collection of assets. In the absence of explicitly defined assets, encompasses all assets within the repository. Likewise, in the absence of explicitly defined strategies, encompasses all the registered strategies. + +## type [DataReport]() + +DataReport is the bactest data report enablign programmatic access to the backtest results. + +```go +type DataReport struct { + // Results are the backtest results for the assets. + Results map[string][]*DataStrategyResult +} +``` + + +### func [NewDataReport]() + +```go +func NewDataReport() *DataReport +``` + +NewDataReport initializes a new data report instance. + + +### func \(\*DataReport\) [AssetBegin]() + +```go +func (d *DataReport) AssetBegin(name string, strategies []strategy.Strategy) error +``` + +AssetBegin is called when backtesting for the given asset begins. + + +### func \(\*DataReport\) [AssetEnd]() + +```go +func (*DataReport) AssetEnd(_ string) error +``` + +AssetEnd is called when backtesting for the given asset ends. + + +### func \(\*DataReport\) [Begin]() + +```go +func (*DataReport) Begin(_ []string, _ []strategy.Strategy) error +``` + +Begin is called when the backtest begins. + + +### func \(\*DataReport\) [End]() + +```go +func (*DataReport) End() error +``` + +End is called when the backtest ends. + + +### func \(\*DataReport\) [Write]() + +```go +func (d *DataReport) Write(assetName string, currentStrategy strategy.Strategy, snapshots <-chan *asset.Snapshot, actions <-chan strategy.Action, outcomes <-chan float64) error +``` + +Write writes the given strategy actions and outomes to the report. + + +## type [DataStrategyResult]() + +DataStrategyResult is the strategy result. + +```go +type DataStrategyResult struct { + // Asset is the asset name. + Asset string + + // Strategy is the strategy instnace. + Strategy strategy.Strategy + + // Outcome is the strategy outcome. + Outcome float64 + + // Action is the final action recommended by the strategy. + Action strategy.Action + + // Transactions are the action recommendations. + Transactions []strategy.Action +} +``` + ## type [HTMLReport]() -HTMLReport is the backtest HTML report interface. +HTMLReport is the backtest HTML report. ```go type HTMLReport struct { @@ -198,4 +317,22 @@ type Report interface { } ``` + +### func [NewReport]() + +```go +func NewReport(name, config string) (Report, error) +``` + +NewReport builds a new report by the given name type and the configuration. + + +## type [ReportBuilderFunc]() + +ReportBuilderFunc defines a function to build a new report using the given configuration parameter. + +```go +type ReportBuilderFunc func(config string) (Report, error) +``` + Generated by [gomarkdoc]() diff --git a/backtest/data_report.go b/backtest/data_report.go new file mode 100644 index 0000000..90ae8db --- /dev/null +++ b/backtest/data_report.go @@ -0,0 +1,86 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package backtest + +import ( + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" +) + +// DataStrategyResult is the strategy result. +type DataStrategyResult struct { + // Asset is the asset name. + Asset string + + // Strategy is the strategy instnace. + Strategy strategy.Strategy + + // Outcome is the strategy outcome. + Outcome float64 + + // Action is the final action recommended by the strategy. + Action strategy.Action + + // Transactions are the action recommendations. + Transactions []strategy.Action +} + +// DataReport is the bactest data report enablign programmatic access to the backtest results. +type DataReport struct { + // Results are the backtest results for the assets. + Results map[string][]*DataStrategyResult +} + +// NewDataReport initializes a new data report instance. +func NewDataReport() *DataReport { + return &DataReport{ + Results: make(map[string][]*DataStrategyResult), + } +} + +// Begin is called when the backtest begins. +func (*DataReport) Begin(_ []string, _ []strategy.Strategy) error { + return nil +} + +// AssetBegin is called when backtesting for the given asset begins. +func (d *DataReport) AssetBegin(name string, strategies []strategy.Strategy) error { + d.Results[name] = make([]*DataStrategyResult, 0, len(strategies)) + return nil +} + +// Write writes the given strategy actions and outomes to the report. +func (d *DataReport) Write(assetName string, currentStrategy strategy.Strategy, snapshots <-chan *asset.Snapshot, actions <-chan strategy.Action, outcomes <-chan float64) error { + go helper.Drain(snapshots) + + actionsSplice := helper.Duplicate(actions, 2) + + lastOutcome := helper.Last(outcomes, 1) + lastAction := helper.Last(actionsSplice[0], 1) + transactions := helper.ChanToSlice(actionsSplice[1]) + + result := &DataStrategyResult{ + Asset: assetName, + Strategy: currentStrategy, + Outcome: <-lastOutcome, + Action: <-lastAction, + Transactions: transactions, + } + + d.Results[assetName] = append(d.Results[assetName], result) + + return nil +} + +// AssetEnd is called when backtesting for the given asset ends. +func (*DataReport) AssetEnd(_ string) error { + return nil +} + +// End is called when the backtest ends. +func (*DataReport) End() error { + return nil +} diff --git a/backtest/data_report_test.go b/backtest/data_report_test.go new file mode 100644 index 0000000..bbbf6ba --- /dev/null +++ b/backtest/data_report_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2021-2024 Onur Cinar. +// The source code is provided under GNU AGPLv3 License. +// https://github.com/cinar/indicator + +package backtest_test + +import ( + "testing" + + "github.com/cinar/indicator/v2/asset" + "github.com/cinar/indicator/v2/backtest" + "github.com/cinar/indicator/v2/helper" + "github.com/cinar/indicator/v2/strategy" +) + +func TestDataReport(t *testing.T) { + repository := asset.NewFileSystemRepository("testdata/repository") + + assets := []string{ + "brk-b", + } + + strategies := []strategy.Strategy{ + strategy.NewBuyAndHoldStrategy(), + } + + report := backtest.NewDataReport() + + err := report.Begin(assets, strategies) + if err != nil { + t.Fatal(err) + } + + err = report.AssetBegin(assets[0], strategies) + if err != nil { + t.Fatal(err) + } + + snapshots, err := repository.Get(assets[0]) + if err != nil { + t.Fatal(err) + } + + snapshotsSplice := helper.Duplicate(snapshots, 3) + actionsSplice := helper.Duplicate( + strategies[0].Compute(snapshotsSplice[1]), + 2, + ) + + outcomes := strategy.Outcome( + asset.SnapshotsAsClosings(snapshotsSplice[2]), + actionsSplice[1], + ) + + err = report.Write(assets[0], strategies[0], snapshotsSplice[0], actionsSplice[0], outcomes) + if err != nil { + t.Fatal(err) + } + + err = report.AssetEnd(assets[0]) + if err != nil { + t.Fatal(err) + } + + err = report.End() + if err != nil { + t.Fatal(err) + } + + results, ok := report.Results[assets[0]] + if !ok { + t.Fatal("asset result not found") + } + + if len(results) != len(strategies) { + t.Fatalf("results count and strategies count are not the same, %d %d", len(results), len(strategies)) + } +} diff --git a/backtest/data_strategy_result.go b/backtest/data_strategy_result.go new file mode 100644 index 0000000..990ba97 --- /dev/null +++ b/backtest/data_strategy_result.go @@ -0,0 +1 @@ +package backtest