/
strategy.go
124 lines (109 loc) · 2.29 KB
/
strategy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package strategy
import (
"log"
"go.oneofone.dev/ta"
"go.oneofone.dev/ta/decimal"
)
type Decimal = decimal.Decimal
type Engine interface {
Start(onBuy, onSell func() (shares int, pricePerShare Decimal))
Stop() (shares int, pricePershare, availableBalance Decimal)
}
type Candle struct {
Open Decimal
High Decimal
Low Decimal
Close Decimal
Volume int
}
type Strategy interface {
Setup(candles []*Candle)
Update(*Candle) (buy, sell bool)
}
type Tx struct {
initial Decimal
Value Decimal
LastPrice Decimal
Bought int
Sold int
Shorted int
Held int
}
func (t *Tx) Total() Decimal {
return t.Value + (Decimal(t.Held) * t.LastPrice)
}
// PL - Profit / Loss
func (t *Tx) PL() Decimal {
return t.Total() - t.initial
}
// PLPerc - Profit/Loss percent
func (t *Tx) PLPerc() Decimal {
return ((t.PL() / t.Total()) * 100).Floor(100)
}
func ApplySlice(acc Account, str Strategy, symbol string, data *ta.TA) *Tx {
inp := make(chan *Candle, 1)
go func() {
for i := 0; i < data.Len(); i++ {
inp <- &Candle{Close: data.Get(i)}
}
close(inp)
}()
var last *Tx
for t := range Apply(acc, str, symbol, inp) {
last = &t
}
return last
}
func Apply(acc Account, str Strategy, symbol string, src <-chan *Candle) <-chan Tx {
ch := make(chan Tx, len(src))
go func() {
defer close(ch)
initial, _, _ := acc.Balance()
tx := Tx{
initial: initial,
Held: acc.Shares(symbol),
}
for c := range src {
shouldBuy, shouldSell := str.Update(c)
if tx.LastPrice == 0 {
tx.Value = tx.initial + (Decimal(tx.Held) * c.Close)
}
tx.LastPrice = c.Close
if shouldBuy && shouldSell {
log.Printf("[strategy] %T.Update() returned both buy and sell", str)
shouldBuy = false
}
if shouldBuy {
shares, pricePerShare := acc.Buy(symbol, c.Close)
if shares == 0 {
continue
}
tx.Bought += shares
tx.Held += shares
tx.Value -= Decimal(shares) * pricePerShare
select {
case ch <- tx:
default:
}
}
if shouldSell {
shares, pricePerShare := acc.Sell(symbol, c.Close)
if shares == 0 {
continue
}
tx.Sold += shares
tx.Held -= shares
tx.Value += Decimal(shares) * pricePerShare
select {
case ch <- tx:
default:
}
}
}
select {
case ch <- tx:
default:
}
}()
return ch
}