Skip to content

Commit

Permalink
Stop loss strategy added. (#203)
Browse files Browse the repository at this point in the history
# Describe Request

Stop loss strategy added.

# Change Type

New strategy.
  • Loading branch information
cinar authored Sep 1, 2024
1 parent 7a0b2bf commit 23c7b8d
Show file tree
Hide file tree
Showing 5 changed files with 455 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ Decorator strategies offer a way to alter the recommendations of other strategie

- [Inverse Strategy](strategy/decorator/README.md#type-inversestrategy)
- [No Loss Strategy](strategy/decorator/README.md#type-nolossstrategy)
- [Stop Loss Strategy](strategy/decorator/README.md#type-stoplossstrategy)

🗃 Repositories
--------------
Expand Down
58 changes: 58 additions & 0 deletions strategy/decorator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ The information provided on this project is strictly for informational purposes
- [func \(n \*NoLossStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#NoLossStrategy.Compute>)
- [func \(n \*NoLossStrategy\) Name\(\) string](<#NoLossStrategy.Name>)
- [func \(n \*NoLossStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#NoLossStrategy.Report>)
- [type StopLossStrategy](<#StopLossStrategy>)
- [func NewStopLossStrategy\(innerStrategy strategy.Strategy, percentage float64\) \*StopLossStrategy](<#NewStopLossStrategy>)
- [func \(s \*StopLossStrategy\) Compute\(snapshots \<\-chan \*asset.Snapshot\) \<\-chan strategy.Action](<#StopLossStrategy.Compute>)
- [func \(s \*StopLossStrategy\) Name\(\) string](<#StopLossStrategy.Name>)
- [func \(s \*StopLossStrategy\) Report\(c \<\-chan \*asset.Snapshot\) \*helper.Report](<#StopLossStrategy.Report>)


<a name="InverseStrategy"></a>
Expand Down Expand Up @@ -136,4 +141,57 @@ func (n *NoLossStrategy) Report(c <-chan *asset.Snapshot) *helper.Report

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

<a name="StopLossStrategy"></a>
## type [StopLossStrategy](<https://github.com/cinar/indicator/blob/master/strategy/decorator/stop_loss_strategy.go#L16-L24>)

StopLossStrategy prevents a loss by recommending a sell action when the assets drops below the given threshold.

```go
type StopLossStrategy struct {
strategy.Strategy

// InnertStrategy is the inner strategy.
InnertStrategy strategy.Strategy

// Percentage is the loss threshold in percentage.
Percentage float64
}
```

<a name="NewStopLossStrategy"></a>
### func [NewStopLossStrategy](<https://github.com/cinar/indicator/blob/master/strategy/decorator/stop_loss_strategy.go#L27>)

```go
func NewStopLossStrategy(innerStrategy strategy.Strategy, percentage float64) *StopLossStrategy
```

NewStopLossStrategy function initializes a new stop loss strategy instance.

<a name="StopLossStrategy.Compute"></a>
### func \(\*StopLossStrategy\) [Compute](<https://github.com/cinar/indicator/blob/master/strategy/decorator/stop_loss_strategy.go#L40>)

```go
func (s *StopLossStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action
```

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

<a name="StopLossStrategy.Name"></a>
### func \(\*StopLossStrategy\) [Name](<https://github.com/cinar/indicator/blob/master/strategy/decorator/stop_loss_strategy.go#L35>)

```go
func (s *StopLossStrategy) Name() string
```

Name returns the name of the strategy.

<a name="StopLossStrategy.Report"></a>
### func \(\*StopLossStrategy\) [Report](<https://github.com/cinar/indicator/blob/master/strategy/decorator/stop_loss_strategy.go#L65>)

```go
func (s *StopLossStrategy) Report(c <-chan *asset.Snapshot) *helper.Report
```

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

Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
84 changes: 84 additions & 0 deletions strategy/decorator/stop_loss_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2021-2024 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package decorator

import (
"fmt"

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

// StopLossStrategy prevents a loss by recommending a sell action when the assets drops below the given threshold.
type StopLossStrategy struct {
strategy.Strategy

// InnertStrategy is the inner strategy.
InnertStrategy strategy.Strategy

// Percentage is the loss threshold in percentage.
Percentage float64
}

// NewStopLossStrategy function initializes a new stop loss strategy instance.
func NewStopLossStrategy(innerStrategy strategy.Strategy, percentage float64) *StopLossStrategy {
return &StopLossStrategy{
InnertStrategy: innerStrategy,
Percentage: percentage,
}
}

// Name returns the name of the strategy.
func (s *StopLossStrategy) Name() string {
return fmt.Sprintf("Stop Loss Strategy (%s)", s.InnertStrategy.Name())
}

// Compute processes the provided asset snapshots and generates a stream of actionable recommendations.
func (s *StopLossStrategy) Compute(snapshots <-chan *asset.Snapshot) <-chan strategy.Action {
snapshotsSplice := helper.Duplicate(snapshots, 2)

innerActions := s.InnertStrategy.Compute(snapshotsSplice[0])
closings := asset.SnapshotsAsClosings(snapshotsSplice[1])
stopLossAt := 0.0

return helper.Operate(innerActions, closings, func(action strategy.Action, closing float64) strategy.Action {
// If action is Buy and the asset is not yet bought, buy it as recommended.
if action == strategy.Buy && stopLossAt == 0.0 {
stopLossAt = closing * (1 - s.Percentage)
return strategy.Buy
}

// If asset is bought and action is sell or closing is less than or equal to stop loss at, recommend sell.
if stopLossAt != 0 && (action == strategy.Sell || closing <= stopLossAt) {
stopLossAt = 0.0
return strategy.Sell
}

return strategy.Hold
})
}

// Report processes the provided asset snapshots and generates a report annotated with the recommended actions.
func (s *StopLossStrategy) Report(c <-chan *asset.Snapshot) *helper.Report {
snapshots := helper.Duplicate(c, 3)

dates := asset.SnapshotsAsDates(snapshots[0])
closings := asset.SnapshotsAsClosings(snapshots[1])

actions, outcomes := strategy.ComputeWithOutcome(s, snapshots[2])
annotations := strategy.ActionsToAnnotations(actions)
outcomes = helper.MultiplyBy(outcomes, 100)

report := helper.NewReport(s.Name(), dates)
report.AddChart()

report.AddColumn(helper.NewNumericReportColumn("Close", closings))
report.AddColumn(helper.NewAnnotationReportColumn(annotations))

report.AddColumn(helper.NewNumericReportColumn("Outcome", outcomes), 1)

return report
}
60 changes: 60 additions & 0 deletions strategy/decorator/stop_loss_strategy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2021-2024 Onur Cinar.
// The source code is provided under GNU AGPLv3 License.
// https://github.com/cinar/indicator

package decorator_test

import (
"os"
"testing"

"github.com/cinar/indicator/v2/asset"
"github.com/cinar/indicator/v2/helper"
"github.com/cinar/indicator/v2/strategy"
"github.com/cinar/indicator/v2/strategy/decorator"
"github.com/cinar/indicator/v2/strategy/trend"
)

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

results, err := helper.ReadFromCsvFile[strategy.Result]("testdata/stop_loss_strategy.csv", true)
if err != nil {
t.Fatal(err)
}

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

innerStrategy := trend.NewAroonStrategy()
strategy := decorator.NewStopLossStrategy(innerStrategy, 0.02)

actual := strategy.Compute(snapshots)

err = helper.CheckEquals(actual, expected)
if err != nil {
t.Fatal(err)
}
}

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

innerStrategy := trend.NewAroonStrategy()
strategy := decorator.NewStopLossStrategy(innerStrategy, 0.02)

report := strategy.Report(snapshots)

fileName := "stop_loss_strategy.html"
defer os.Remove(fileName)

err = report.WriteToFile(fileName)
if err != nil {
t.Fatal(err)
}
}
Loading

0 comments on commit 23c7b8d

Please sign in to comment.