Skip to content

Commit

Permalink
Apo strategy implementing the strategy interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
cinar committed Dec 23, 2023
1 parent e0ce63b commit 47aafaa
Show file tree
Hide file tree
Showing 4 changed files with 571 additions and 296 deletions.
70 changes: 48 additions & 22 deletions strategy/apo_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package strategy
import (
"time"

"github.com/cinar/indicator/asset"
"github.com/cinar/indicator/helper"
"github.com/cinar/indicator/trend"
)
Expand All @@ -15,31 +16,40 @@ import (
// 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.
type ApoStrategy[T helper.Number] struct {
type ApoStrategy struct {
// Apo represents the configuration parameters for calculating the
// Absolute Price Oscillator (APO).
Apo *trend.Apo[T]
Apo *trend.Apo[float64]
}

// NewApoStrategy function initializes a new APO strategy instance with the default parameters.
func NewApoStrategy[T helper.Number]() *ApoStrategy[T] {
return &ApoStrategy[T]{
Apo: trend.NewApo[T](),
func NewApoStrategy() *ApoStrategy {
return &ApoStrategy{
Apo: trend.NewApo[float64](),
}
}

// Compute uses the given values as input and returns a channel of
// action recommendations.
func (a *ApoStrategy[T]) Compute(c <-chan T) <-chan Action {
apo := a.Apo.Compute(c)
// Name returns the name of the strategy.
func (a *ApoStrategy) Name() string {
return "Apo Strategy"
}

// Compute processes the provided asset snapshots and generates a
// stream of actionable recommendations,
func (a *ApoStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan Action {
closing := helper.Map(snapshots, func(snapshot *asset.Snapshot) float64 {
return snapshot.Close
})

apo := a.Apo.Compute(closing)
apo = helper.Buffered(apo, 2)

inputs := helper.Duplicate(apo, 2)

// Ship the first value
inputs[1] = helper.Skip(inputs[1], 1)

return helper.Shift(helper.Operate(inputs[0], inputs[1], func(b, c T) Action {
return helper.Shift(helper.Operate(inputs[0], inputs[1], func(b, c float64) Action {
// An APO value crossing above zero suggests a bullish trend.
if c >= 0 && b < 0 {
return Buy
Expand All @@ -54,26 +64,42 @@ func (a *ApoStrategy[T]) Compute(c <-chan T) <-chan Action {
}), 1, Hold)
}

// Report takes input channels containing dates and values, computes the recommended actions
// based on the data, and generates a report annotated with those actions.
func (a *ApoStrategy[T]) Report(dates <-chan time.Time, c <-chan T) *helper.Report {
cs := helper.Duplicate(c, 4)
// Report processes the provided asset snapshots and generates a
// report annotated with the recommended actions.
func (a *ApoStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
//
// snapshots[0] -> dates
// snapshots[1] -> Compute -> actions[0] -> annotations
// -> actions[1] |> outcome
// snapshots[2] -> closings[2] -> |
// -> closings[0] -> close
// -> closings[1] -> Apo.Compute -> apo
//
snapshots := helper.Duplicate(c, 3)

dates := helper.Map(snapshots[0], func(snapshot *asset.Snapshot) time.Time {
return snapshot.Date
})

closings := helper.Duplicate(helper.Map(snapshots[2], func(snapshot *asset.Snapshot) float64 {
return snapshot.Close
}), 3)

dates = helper.Skip(dates, a.Apo.SlowPeriod-1)
cs[0] = helper.Skip(cs[0], a.Apo.SlowPeriod-1)
cs[3] = helper.Skip(cs[3], a.Apo.SlowPeriod-1)
apo := a.Apo.Compute(cs[1])

actions := helper.Duplicate(a.Compute(cs[2]), 2)
annotations := ActionsToAnnotations(actions[0])
closings[0] = helper.Skip(closings[0], a.Apo.SlowPeriod-1)
closings[2] = helper.Skip(closings[2], a.Apo.SlowPeriod-1)
apo := a.Apo.Compute(closings[1])

outcome := Outcome(cs[3], actions[1])
actions := helper.Duplicate(a.Compute(snapshots[1]), 2)
annotations := ActionsToAnnotations(actions[0])
outcome := Outcome(closings[2], actions[1])

report := helper.NewReport("APO Strategy", dates)
report := helper.NewReport(a.Name(), dates)
report.AddChart()
report.AddChart()

report.AddColumn(helper.NewNumericReportColumn("Close", cs[0]))
report.AddColumn(helper.NewNumericReportColumn("Close", closings[0]))
report.AddColumn(helper.NewNumericReportColumn("APO", apo), 1)
report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1)

Expand Down
41 changes: 19 additions & 22 deletions strategy/apo_strategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,38 @@
package strategy_test

import (
"os"
"testing"
"time"

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

func TestApoStrategy(t *testing.T) {
type Row struct {
Close float64
type Result struct {
Action strategy.Action
}

input, err := helper.ReadFromCsvFile[Row]("testdata/apo_strategy.csv", true)
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true)
if err != nil {
t.Fatal(err)
}

inputs := helper.Duplicate(input, 2)
closing := helper.Map(inputs[0], func(row *Row) float64 { return row.Close })
expected := helper.Map(inputs[1], func(row *Row) strategy.Action { return row.Action })
results, err := helper.ReadFromCsvFile[Result]("testdata/apo_strategy.csv", true)
if err != nil {
t.Fatal(err)
}

apo := strategy.NewApoStrategy()

expected := helper.Map(results, func(r *Result) strategy.Action {
return r.Action
})

apo := strategy.NewApoStrategy[float64]()
expected = helper.Skip(expected, apo.Apo.SlowPeriod-1)

actual := apo.Compute(closing)
actual := apo.Compute(snapshots)

err = helper.CheckEquals(actual, expected)
if err != nil {
Expand All @@ -39,26 +45,17 @@ func TestApoStrategy(t *testing.T) {
}

func TestApoStrategyReport(t *testing.T) {
type Row struct {
Date time.Time `format:"2006-01-02"`
Close float64
}

input, err := helper.ReadFromCsvFile[Row]("testdata/apo_strategy.csv", true)
snapshots, err := helper.ReadFromCsvFile[asset.Snapshot]("testdata/brk-b.csv", true)
if err != nil {
t.Fatal(err)
}

inputs := helper.Duplicate(input, 2)
dates := helper.Map(inputs[0], func(row *Row) time.Time { return row.Date })
closing := helper.Map(inputs[1], func(row *Row) float64 { return row.Close })

apo := strategy.NewApoStrategy[float64]()
apo := strategy.NewApoStrategy()

report := apo.Report(dates, closing)
report := apo.Report(snapshots)

fileName := "apo_strategy.html"
// defer os.Remove(fileName)
defer os.Remove(fileName)

err = report.WriteToFile(fileName)
if err != nil {
Expand Down
Loading

0 comments on commit 47aafaa

Please sign in to comment.