Skip to content

Commit

Permalink
Backtest added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Dec 25, 2023
1 parent 62c7dc2 commit e85b594
Show file tree
Hide file tree
Showing 16 changed files with 845 additions and 318 deletions.
22 changes: 21 additions & 1 deletion asset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ The information provided on this project is strictly for informational purposes

## Index

- [func SnapshotsAsClosings\(snapshots \<\-chan \*Snapshot\) \<\-chan float64](<#SnapshotsAsClosings>)
- [func SnapshotsAsDates\(snapshots \<\-chan \*Snapshot\) \<\-chan time.Time](<#SnapshotsAsDates>)
- [func Sync\(source, target Repository, defaultStartDate time.Time, workers int\) error](<#Sync>)
- [type FileSystemRepository](<#FileSystemRepository>)
- [func NewFileSystemRepository\(base string\) \*FileSystemRepository](<#NewFileSystemRepository>)
Expand Down Expand Up @@ -53,6 +55,24 @@ The information provided on this project is strictly for informational purposes
- [func \(r \*TiingoRepository\) LastDate\(name string\) \(time.Time, error\)](<#TiingoRepository.LastDate>)


<a name="SnapshotsAsClosings"></a>
## func [SnapshotsAsClosings](<https://github.com/cinar/indicator/blob/v2/asset/snapshot.go#L52>)

```go
func SnapshotsAsClosings(snapshots <-chan *Snapshot) <-chan float64
```

SnapshotsAsClosings extracts the close field from each snapshot in the provided channel and returns a new channel containing only those close values.The original snapshots channel can no longer be directly used afterwards.

<a name="SnapshotsAsDates"></a>
## func [SnapshotsAsDates](<https://github.com/cinar/indicator/blob/v2/asset/snapshot.go#L43>)

```go
func SnapshotsAsDates(snapshots <-chan *Snapshot) <-chan time.Time
```

SnapshotsAsDates extracts the date field from each snapshot in the provided channel and returns a new channel containing only those date values.The original snapshots channel can no longer be directly used afterwards.

<a name="Sync"></a>
## func [Sync](<https://github.com/cinar/indicator/blob/v2/asset/sync.go#L18>)

Expand Down Expand Up @@ -223,7 +243,7 @@ type Repository interface {
```

<a name="Snapshot"></a>
## type [Snapshot](<https://github.com/cinar/indicator/blob/v2/asset/snapshot.go#L13-L36>)
## type [Snapshot](<https://github.com/cinar/indicator/blob/v2/asset/snapshot.go#L15-L38>)

Snapshot captures a single observation of an asset's price at a specific moment.

Expand Down
23 changes: 13 additions & 10 deletions asset/file_system_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package asset_test
import (
"fmt"
"os"
"path"
"reflect"
"testing"
"time"
Expand All @@ -15,9 +16,11 @@ import (
"github.com/cinar/indicator/helper"
)

var repositoryBase = "testdata/repository"

func TestFileSystemRepositoryAssets(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
expected := []string{"brk-b", "empty", "since"}
repository := asset.NewFileSystemRepository(repositoryBase)
expected := []string{"brk-b"}

actual, err := repository.Assets()
if err != nil {
Expand All @@ -39,7 +42,7 @@ func TestFileSystemRepositoryAssetsNonExisting(t *testing.T) {
}

func TestFileSystemRepositoryGet(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

snapshots, err := repository.Get("brk-b")
if err != nil {
Expand All @@ -59,7 +62,7 @@ func TestFileSystemRepositoryGetNonExisting(t *testing.T) {
}

func TestFileSystemRepositoryGetSince(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

date := time.Date(2022, 12, 20, 0, 0, 0, 0, time.UTC)
actual, err := repository.GetSince("brk-b", date)
Expand All @@ -79,7 +82,7 @@ func TestFileSystemRepositoryGetSince(t *testing.T) {
}

func TestFileSystemRepositoryGetSinceNonExisting(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

date := time.Date(2022, 12, 01, 0, 0, 0, 0, time.UTC)
_, err := repository.GetSince("brk", date)
Expand All @@ -91,7 +94,7 @@ func TestFileSystemRepositoryGetSinceNonExisting(t *testing.T) {
func TestFileSystemRepositoryLastDate(t *testing.T) {
expeted := time.Date(2022, 12, 30, 0, 0, 0, 0, time.UTC)

repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

actual, err := repository.LastDate("brk-b")
if err != nil {
Expand All @@ -104,7 +107,7 @@ func TestFileSystemRepositoryLastDate(t *testing.T) {
}

func TestFileSystemRepositoryLastDateNonExisting(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

_, err := repository.LastDate("brk")
if err == nil {
Expand All @@ -113,7 +116,7 @@ func TestFileSystemRepositoryLastDateNonExisting(t *testing.T) {
}

func TestFileSystemRepositoryLastDateEmpty(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

_, err := repository.LastDate("empty")
if err == nil {
Expand All @@ -122,15 +125,15 @@ func TestFileSystemRepositoryLastDateEmpty(t *testing.T) {
}

func TestFileSystemRepositoryAppend(t *testing.T) {
repository := asset.NewFileSystemRepository("testdata")
repository := asset.NewFileSystemRepository(repositoryBase)

expected, err := repository.Get("brk-b")
if err != nil {
t.Fatal(err)
}

name := "test_file_system_repository_append"
defer os.Remove(fmt.Sprintf("testdata/%s.csv", name))
defer os.Remove(path.Join(repositoryBase, fmt.Sprintf("%s.csv", name)))

err = repository.Append(name, expected)
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions asset/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package asset

import (
"time"

"github.com/cinar/indicator/helper"
)

// Snapshot captures a single observation of an asset's price
Expand Down Expand Up @@ -34,3 +36,21 @@ type Snapshot struct {
// the asset during the snapshot period.
Volume int64
}

// SnapshotsAsDates extracts the date field from each snapshot in the provided
// channel and returns a new channel containing only those date values.The
// original snapshots channel can no longer be directly used afterwards.
func SnapshotsAsDates(snapshots <-chan *Snapshot) <-chan time.Time {
return helper.Map(snapshots, func(snapshot *Snapshot) time.Time {
return snapshot.Date
})
}

// SnapshotsAsClosings extracts the close field from each snapshot in the provided
// channel and returns a new channel containing only those close values.The
// original snapshots channel can no longer be directly used afterwards.
func SnapshotsAsClosings(snapshots <-chan *Snapshot) <-chan float64 {
return helper.Map(snapshots, func(snapshot *Snapshot) float64 {
return snapshot.Close
})
}
37 changes: 37 additions & 0 deletions asset/snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2023 Onur Cinar. All Rights Reserved.
// The source code is provided under MIT License.
// https://github.com/cinar/indicator

package asset_test

import (
"testing"

"github.com/cinar/indicator/asset"
"github.com/cinar/indicator/helper"
)

func TestSnapshotsAs(t *testing.T) {
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/repository/brk-b.csv", true)
if err != nil {
t.Fatal(err)
}

snapshotsCopies := helper.Duplicate(snapshots, 3)

dates := asset.SnapshotsAsDates(snapshotsCopies[1])
closings := asset.SnapshotsAsClosings(snapshotsCopies[2])

for snapshot := range snapshotsCopies[0] {
date := <-dates
closing := <-closings

if !date.Equal(snapshot.Date) {
t.Fatalf("actual %v expected %v", date, snapshot.Date)
}

if closing != snapshot.Close {
t.Fatalf("actual %v expected %v", closing, snapshot.Close)
}
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion helper/report.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@

{{ range .Date }}
data.addRow([
new Date({{ .Format "2006, 1, 2" }}),
new Date("{{ .Format "2006-01-02" }}"),
{{ range $.Columns }}
{{ .Value }},
{{ end }}
Expand Down
61 changes: 51 additions & 10 deletions strategy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ The information provided on this project is strictly for informational purposes
- [func \(a Action\) Annotation\(\) string](<#Action.Annotation>)
- [type ApoStrategy](<#ApoStrategy>)
- [func NewApoStrategy\(\) \*ApoStrategy](<#NewApoStrategy>)
- [func \(a \*ApoStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan Action](<#ApoStrategy.Compute>)
- [func \(a \*ApoStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \(\<\-chan Action, \<\-chan float64\)](<#ApoStrategy.Compute>)
- [func \(a \*ApoStrategy\) Name\(\) string](<#ApoStrategy.Name>)
- [func \(a \*ApoStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#ApoStrategy.Report>)
- [type Backtest](<#Backtest>)
- [func NewBacktest\(repository asset.Repository, outputDir string\) \*Backtest](<#NewBacktest>)
- [func \(b \*Backtest\) Run\(\) error](<#Backtest.Run>)
- [type Strategy](<#Strategy>)


Expand Down Expand Up @@ -113,7 +116,7 @@ func (a Action) Annotation() string
Annotation returns a single character string representing the recommended action. It returns "S" for Sell, "B" for Buy, and an empty string for Hold.

<a name="ApoStrategy"></a>
## type [ApoStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L19-L23>)
## type [ApoStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L17-L21>)

ApoStrategy represents the configuration parameters for calculating the APO strategy. An APO value crossing above zero suggests a bullish trend, while crossing below zero indicates a bearish trend. Positive APO values signify an upward trend, while negative values signify a downward trend.

Expand All @@ -126,7 +129,7 @@ type ApoStrategy struct {
```

<a name="NewApoStrategy"></a>
### func [NewApoStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L26>)
### func [NewApoStrategy](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L24>)

```go
func NewApoStrategy() *ApoStrategy
Expand All @@ -135,16 +138,16 @@ func NewApoStrategy() *ApoStrategy
NewApoStrategy function initializes a new APO strategy instance with the default parameters.

<a name="ApoStrategy.Compute"></a>
### func \(\*ApoStrategy\) [Compute](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L39>)
### func \(\*ApoStrategy\) [Compute](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L37>)

```go
func (a *ApoStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan Action
func (a *ApoStrategy) Compute(snapshots <-chan *asset.Snapshot) (<-chan Action, <-chan float64)
```

Compute processes the provided asset snapshots and generates a stream of actionable recommendations,
Compute processes the provided asset snapshots and generates a stream of actionable recommendations and outcomes.

<a name="ApoStrategy.Name"></a>
### func \(\*ApoStrategy\) [Name](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L33>)
### func \(\*ApoStrategy\) [Name](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L31>)

```go
func (a *ApoStrategy) Name() string
Expand All @@ -153,14 +156,52 @@ func (a *ApoStrategy) Name() string
Name returns the name of the strategy.

<a name="ApoStrategy.Report"></a>
### func \(\*ApoStrategy\) [Report](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L69>)
### func \(\*ApoStrategy\) [Report](<https://github.com/cinar/indicator/blob/v2/strategy/apo_strategy.go#L73>)

```go
func (a *ApoStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
```

Report processes the provided asset snapshots and generates a report annotated with the recommended actions.

<a name="Backtest"></a>
## type [Backtest](<https://github.com/cinar/indicator/blob/v2/strategy/backtest.go#L32-L47>)

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

// Workers is the number of concurrent workers.
Workers int
// contains filtered or unexported fields
}
```

<a name="NewBacktest"></a>
### func [NewBacktest](<https://github.com/cinar/indicator/blob/v2/strategy/backtest.go#L65>)

```go
func NewBacktest(repository asset.Repository, outputDir string) *Backtest
```

NewBacktest function initializes a new backtest instance.

<a name="Backtest.Run"></a>
### func \(\*Backtest\) [Run](<https://github.com/cinar/indicator/blob/v2/strategy/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="Strategy"></a>
## type [Strategy](<https://github.com/cinar/indicator/blob/v2/strategy/strategy.go#L27-L38>)

Expand All @@ -172,8 +213,8 @@ type Strategy interface {
Name() string

// Compute processes the provided asset snapshots and generates a
// stream of actionable recommendations,
Compute(snapshots <-chan *asset.Snapshot) <-chan Action
// stream of actionable recommendations and outcomes.
Compute(snapshots <-chan *asset.Snapshot) (<-chan Action, <-chan float64)

// Report processes the provided asset snapshots and generates a
// report annotated with the recommended actions.
Expand Down
Loading

0 comments on commit e85b594

Please sign in to comment.