Skip to content

Commit

Permalink
Move HTML backtest report out of the backtest to enable other type of…
Browse files Browse the repository at this point in the history
… 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
cinar authored Sep 7, 2024
1 parent 7076260 commit 87f1acd
Show file tree
Hide file tree
Showing 14 changed files with 726 additions and 386 deletions.
201 changes: 201 additions & 0 deletions backtest/README.md
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>)
167 changes: 167 additions & 0 deletions backtest/backtest.go
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)
}
}
}
Loading

0 comments on commit 87f1acd

Please sign in to comment.