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