Skip to content

Commit

Permalink
Data report is added. (#218)
Browse files Browse the repository at this point in the history
# Describe Request

Data report is added to enable the backtest results to be
programmatically accessed.

Fixed #217

# Change Type

New feature.
  • Loading branch information
cinar authored Sep 14, 2024
1 parent f5a88d0 commit 55e4a03
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 1 deletion.
139 changes: 138 additions & 1 deletion backtest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>)
Expand All @@ -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
Expand All @@ -61,6 +72,24 @@ const (
)
```

<a name="HTMLReportBuilderName"></a>

```go
const (
// HTMLReportBuilderName is the name for the HTML report builder.
HTMLReportBuilderName = "html"
)
```

<a name="RegisterReportBuilder"></a>
## func [RegisterReportBuilder](<https://github.com/cinar/indicator/blob/master/backtest/report_factory.go#L25>)

```go
func RegisterReportBuilder(name string, builder ReportBuilderFunc)
```

RegisterReportBuilder registers the given builder.

<a name="Backtest"></a>
## type [Backtest](<https://github.com/cinar/indicator/blob/master/backtest/backtest.go#L43-L61>)

Expand Down Expand Up @@ -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.

<a name="DataReport"></a>
## type [DataReport](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L32-L35>)

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
}
```

<a name="NewDataReport"></a>
### func [NewDataReport](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L38>)

```go
func NewDataReport() *DataReport
```

NewDataReport initializes a new data report instance.

<a name="DataReport.AssetBegin"></a>
### func \(\*DataReport\) [AssetBegin](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L50>)

```go
func (d *DataReport) AssetBegin(name string, strategies []strategy.Strategy) error
```

AssetBegin is called when backtesting for the given asset begins.

<a name="DataReport.AssetEnd"></a>
### func \(\*DataReport\) [AssetEnd](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L79>)

```go
func (*DataReport) AssetEnd(_ string) error
```

AssetEnd is called when backtesting for the given asset ends.

<a name="DataReport.Begin"></a>
### func \(\*DataReport\) [Begin](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L45>)

```go
func (*DataReport) Begin(_ []string, _ []strategy.Strategy) error
```

Begin is called when the backtest begins.

<a name="DataReport.End"></a>
### func \(\*DataReport\) [End](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L84>)

```go
func (*DataReport) End() error
```

End is called when the backtest ends.

<a name="DataReport.Write"></a>
### func \(\*DataReport\) [Write](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L56>)

```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.

<a name="DataStrategyResult"></a>
## type [DataStrategyResult](<https://github.com/cinar/indicator/blob/master/backtest/data_report.go#L14-L29>)

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
}
```

<a name="HTMLReport"></a>
## type [HTMLReport](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L36-L53>)

HTMLReport is the backtest HTML report interface.
HTMLReport is the backtest HTML report.

```go
type HTMLReport struct {
Expand Down Expand Up @@ -198,4 +317,22 @@ type Report interface {
}
```

<a name="NewReport"></a>
### func [NewReport](<https://github.com/cinar/indicator/blob/master/backtest/report_factory.go#L30>)

```go
func NewReport(name, config string) (Report, error)
```

NewReport builds a new report by the given name type and the configuration.

<a name="ReportBuilderFunc"></a>
## type [ReportBuilderFunc](<https://github.com/cinar/indicator/blob/master/backtest/report_factory.go#L17>)

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](<https://github.com/princjef/gomarkdoc>)
86 changes: 86 additions & 0 deletions backtest/data_report.go
Original file line number Diff line number Diff line change
@@ -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
}
78 changes: 78 additions & 0 deletions backtest/data_report_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
}
1 change: 1 addition & 0 deletions backtest/data_strategy_result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package backtest

0 comments on commit 55e4a03

Please sign in to comment.