-
-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move HTML backtest report out of the backtest to enable other type of…
… reports. (#209) # Describe Request Moved HTML backtest report out of the backtest to enable other type of reports. # Change Type New feature.
- Loading branch information
Showing
14 changed files
with
726 additions
and
386 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
<!-- Code generated by gomarkdoc. DO NOT EDIT --> | ||
|
||
# backtest | ||
|
||
```go | ||
import "github.com/cinar/indicator/v2/backtest" | ||
``` | ||
|
||
Package backtest contains the backtest functions. | ||
|
||
This package belongs to the Indicator project. Indicator is a Golang module that supplies a variety of technical indicators, strategies, and a backtesting framework for analysis. | ||
|
||
### License | ||
|
||
``` | ||
Copyright (c) 2021-2024 Onur Cinar. | ||
The source code is provided under GNU AGPLv3 License. | ||
https://github.com/cinar/indicator | ||
``` | ||
|
||
### Disclaimer | ||
|
||
The information provided on this project is strictly for informational purposes and is not to be construed as advice or solicitation to buy or sell any security. | ||
|
||
## Index | ||
|
||
- [Constants](<#constants>) | ||
- [type Backtest](<#Backtest>) | ||
- [func NewBacktest\(repository asset.Repository, report Report\) \*Backtest](<#NewBacktest>) | ||
- [func \(b \*Backtest\) Run\(\) error](<#Backtest.Run>) | ||
- [type HTMLReport](<#HTMLReport>) | ||
- [func NewHTMLReport\(outputDir string\) \*HTMLReport](<#NewHTMLReport>) | ||
- [func \(h \*HTMLReport\) AssetBegin\(name string, strategies \[\]strategy.Strategy\) error](<#HTMLReport.AssetBegin>) | ||
- [func \(h \*HTMLReport\) AssetEnd\(name string\) error](<#HTMLReport.AssetEnd>) | ||
- [func \(h \*HTMLReport\) Begin\(assetNames \[\]string, \_ \[\]strategy.Strategy\) error](<#HTMLReport.Begin>) | ||
- [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>) | ||
|
||
|
||
## Constants | ||
|
||
<a name="DefaultBacktestWorkers"></a> | ||
|
||
```go | ||
const ( | ||
// DefaultBacktestWorkers is the default number of backtest workers. | ||
DefaultBacktestWorkers = 1 | ||
|
||
// DefaultLastDays is the default number of days backtest should go back. | ||
DefaultLastDays = 365 | ||
) | ||
``` | ||
|
||
<a name="DefaultWriteStrategyReports"></a> | ||
|
||
```go | ||
const ( | ||
// DefaultWriteStrategyReports is the default state of writing individual strategy reports. | ||
DefaultWriteStrategyReports = true | ||
) | ||
``` | ||
|
||
<a name="Backtest"></a> | ||
## type [Backtest](<https://github.com/cinar/indicator/blob/master/backtest/backtest.go#L43-L61>) | ||
|
||
Backtest function rigorously evaluates the potential performance of the specified strategies applied to a defined set of assets. It generates comprehensive visual representations for each strategy\-asset pairing. | ||
|
||
```go | ||
type Backtest struct { | ||
|
||
// Names is the names of the assets to backtest. | ||
Names []string | ||
|
||
// Strategies is the list of strategies to apply. | ||
Strategies []strategy.Strategy | ||
|
||
// Workers is the number of concurrent workers. | ||
Workers int | ||
|
||
// LastDays is the number of days backtest should go back. | ||
LastDays int | ||
// contains filtered or unexported fields | ||
} | ||
``` | ||
|
||
<a name="NewBacktest"></a> | ||
### func [NewBacktest](<https://github.com/cinar/indicator/blob/master/backtest/backtest.go#L64>) | ||
|
||
```go | ||
func NewBacktest(repository asset.Repository, report Report) *Backtest | ||
``` | ||
|
||
NewBacktest function initializes a new backtest instance. | ||
|
||
<a name="Backtest.Run"></a> | ||
### func \(\*Backtest\) [Run](<https://github.com/cinar/indicator/blob/master/backtest/backtest.go#L79>) | ||
|
||
```go | ||
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="HTMLReport"></a> | ||
## type [HTMLReport](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L36-L53>) | ||
|
||
HTMLReport is the backtest HTML report interface. | ||
|
||
```go | ||
type HTMLReport struct { | ||
Report | ||
|
||
// 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 | ||
// contains filtered or unexported fields | ||
} | ||
``` | ||
|
||
<a name="NewHTMLReport"></a> | ||
### func [NewHTMLReport](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L77>) | ||
|
||
```go | ||
func NewHTMLReport(outputDir string) *HTMLReport | ||
``` | ||
|
||
NewHTMLReport initializes a new HTML report instance. | ||
|
||
<a name="HTMLReport.AssetBegin"></a> | ||
### func \(\*HTMLReport\) [AssetBegin](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L100>) | ||
|
||
```go | ||
func (h *HTMLReport) AssetBegin(name string, strategies []strategy.Strategy) error | ||
``` | ||
|
||
AssetBegin is called when backtesting for the given asset begins. | ||
|
||
<a name="HTMLReport.AssetEnd"></a> | ||
### func \(\*HTMLReport\) [AssetEnd](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L155>) | ||
|
||
```go | ||
func (h *HTMLReport) AssetEnd(name string) error | ||
``` | ||
|
||
AssetEnd is called when backtesting for the given asset ends. | ||
|
||
<a name="HTMLReport.Begin"></a> | ||
### func \(\*HTMLReport\) [Begin](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L87>) | ||
|
||
```go | ||
func (h *HTMLReport) Begin(assetNames []string, _ []strategy.Strategy) error | ||
``` | ||
|
||
Begin is called when the backtest starts. | ||
|
||
<a name="HTMLReport.End"></a> | ||
### func \(\*HTMLReport\) [End](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L184>) | ||
|
||
```go | ||
func (h *HTMLReport) End() error | ||
``` | ||
|
||
End is called when the backtest ends. | ||
|
||
<a name="HTMLReport.Write"></a> | ||
### func \(\*HTMLReport\) [Write](<https://github.com/cinar/indicator/blob/master/backtest/html_report.go#L112>) | ||
|
||
```go | ||
func (h *HTMLReport) 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="Report"></a> | ||
## type [Report](<https://github.com/cinar/indicator/blob/master/backtest/report.go#L13-L28>) | ||
|
||
Report is the backtest report interface. | ||
|
||
```go | ||
type Report interface { | ||
// Begin is called when the backtest begins. | ||
Begin(assetNames []string, strategies []strategy.Strategy) error | ||
|
||
// AssetBegin is called when backtesting for the given asset begins. | ||
AssetBegin(name string, strategies []strategy.Strategy) error | ||
|
||
// Write writes the given strategy actions and outomes to the report. | ||
Write(assetName string, currentStrategy strategy.Strategy, snapshots <-chan *asset.Snapshot, actions <-chan strategy.Action, outcomes <-chan float64) error | ||
|
||
// AssetEnd is called when backtesting for the given asset ends. | ||
AssetEnd(name string) error | ||
|
||
// End is called when the backtest ends. | ||
End() error | ||
} | ||
``` | ||
|
||
Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Package backtest contains the backtest functions. | ||
// | ||
// This package belongs to the Indicator project. Indicator is | ||
// a Golang module that supplies a variety of technical | ||
// indicators, strategies, and a backtesting framework | ||
// for analysis. | ||
// | ||
// # License | ||
// | ||
// Copyright (c) 2021-2024 Onur Cinar. | ||
// The source code is provided under GNU AGPLv3 License. | ||
// https://github.com/cinar/indicator | ||
// | ||
// # Disclaimer | ||
// | ||
// The information provided on this project is strictly for | ||
// informational purposes and is not to be construed as | ||
// advice or solicitation to buy or sell any security. | ||
package backtest | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"sync" | ||
"time" | ||
|
||
"github.com/cinar/indicator/v2/asset" | ||
"github.com/cinar/indicator/v2/helper" | ||
"github.com/cinar/indicator/v2/strategy" | ||
) | ||
|
||
const ( | ||
// DefaultBacktestWorkers is the default number of backtest workers. | ||
DefaultBacktestWorkers = 1 | ||
|
||
// DefaultLastDays is the default number of days backtest should go back. | ||
DefaultLastDays = 365 | ||
) | ||
|
||
// Backtest function rigorously evaluates the potential performance of the | ||
// specified strategies applied to a defined set of assets. It generates | ||
// comprehensive visual representations for each strategy-asset pairing. | ||
type Backtest struct { | ||
// repository is the repository to retrieve the assets from. | ||
repository asset.Repository | ||
|
||
// report is the report writer for the backtest. | ||
report Report | ||
|
||
// Names is the names of the assets to backtest. | ||
Names []string | ||
|
||
// Strategies is the list of strategies to apply. | ||
Strategies []strategy.Strategy | ||
|
||
// Workers is the number of concurrent workers. | ||
Workers int | ||
|
||
// LastDays is the number of days backtest should go back. | ||
LastDays int | ||
} | ||
|
||
// NewBacktest function initializes a new backtest instance. | ||
func NewBacktest(repository asset.Repository, report Report) *Backtest { | ||
return &Backtest{ | ||
repository: repository, | ||
report: report, | ||
Names: []string{}, | ||
Strategies: []strategy.Strategy{}, | ||
Workers: DefaultBacktestWorkers, | ||
LastDays: DefaultLastDays, | ||
} | ||
} | ||
|
||
// 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. | ||
func (b *Backtest) Run() error { | ||
// When asset names are absent, considers all assets within the provided repository for evaluation. | ||
if len(b.Names) == 0 { | ||
assets, err := b.repository.Assets() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
b.Names = assets | ||
} | ||
|
||
// When strategies are absent, considers all strategies. | ||
if len(b.Strategies) == 0 { | ||
b.Strategies = []strategy.Strategy{ | ||
strategy.NewBuyAndHoldStrategy(), | ||
} | ||
} | ||
|
||
// Begin report. | ||
err := b.report.Begin(b.Names, b.Strategies) | ||
if err != nil { | ||
return fmt.Errorf("unable to begin report: %w", err) | ||
} | ||
|
||
// Run the backtest workers. | ||
names := helper.SliceToChan(b.Names) | ||
wg := &sync.WaitGroup{} | ||
|
||
for i := 0; i < b.Workers; i++ { | ||
wg.Add(1) | ||
go b.worker(names, wg) | ||
} | ||
|
||
// Wait for all workers to finish. | ||
wg.Wait() | ||
|
||
// End report. | ||
err = b.report.End() | ||
if err != nil { | ||
return fmt.Errorf("unable to end report: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// worker is a backtesting worker that concurrently executes backtests for individual | ||
// assets. It receives asset names from the provided channel, and performs backtests | ||
// using the given strategies. | ||
func (b *Backtest) worker(names <-chan string, wg *sync.WaitGroup) { | ||
defer wg.Done() | ||
|
||
since := time.Now().AddDate(0, 0, -b.LastDays) | ||
|
||
for name := range names { | ||
log.Printf("Backtesting %s...", name) | ||
snapshots, err := b.repository.GetSince(name, since) | ||
if err != nil { | ||
log.Printf("Unable to retrieve the snapshots for %s: %v", name, err) | ||
continue | ||
} | ||
|
||
// We don't expect the snapshots to be a stream during backtesting. | ||
snapshotsSlice := helper.ChanToSlice(snapshots) | ||
|
||
// Backtesting asset has begun. | ||
err = b.report.AssetBegin(name, b.Strategies) | ||
if err != nil { | ||
log.Printf("Unable to asset begin for %s: %v", name, err) | ||
continue | ||
} | ||
|
||
// Backtest strategies on the given asset. | ||
for _, currentStrategy := range b.Strategies { | ||
snapshotsSplice := helper.Duplicate(helper.SliceToChan(snapshotsSlice), 2) | ||
|
||
actions, outcomes := strategy.ComputeWithOutcome(currentStrategy, snapshotsSplice[0]) | ||
err = b.report.Write(name, currentStrategy, snapshotsSplice[1], actions, outcomes) | ||
if err != nil { | ||
log.Printf("Unable to report write for %s: %v", name, err) | ||
} | ||
} | ||
|
||
// Backtesting asset had ended | ||
err = b.report.AssetEnd(name) | ||
if err != nil { | ||
log.Printf("Unable to asset end for %s: %v", name, err) | ||
} | ||
} | ||
} |
Oops, something went wrong.