Skip to content

Commit

Permalink
bbands, a few strat changes
Browse files Browse the repository at this point in the history
  • Loading branch information
OneOfOne committed Sep 15, 2020
1 parent 89355e3 commit d0b59c2
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/qos.yml
Expand Up @@ -19,6 +19,7 @@ jobs:
- name: Install Python Deps
shell: bash -l {0}
run: |
conda update -n base -c defaults conda
conda install -c quantopian ta-lib
- name: Install Go Deps
Expand Down Expand Up @@ -48,7 +49,7 @@ jobs:
- uses: shogo82148/actions-goveralls@v1
with:
parallel-finished: true
- name: Install Python Deps
- name: Poke proxy.golang.org
shell: bash -l {0}
run: |
cd $(mktemp -d)
Expand Down
29 changes: 28 additions & 1 deletion decimal/utils.go
@@ -1,6 +1,11 @@
package decimal

import "unsafe"
import (
"time"
"unsafe"

"gonum.org/v1/gonum/floats"
)

func AbsInt(a int) int {
if a < 0 {
Expand Down Expand Up @@ -117,3 +122,25 @@ func SliceToFloats(in []Decimal, copy bool) []float64 {
}
return *(*[]float64)(unsafe.Pointer(&in))
}

func AggPipe(aggPeriod time.Duration, in <-chan Decimal) <-chan Decimal {
ch := make(chan Decimal, 100)
buf := make([]float64, 0, 600)
go func() {
t := time.Now()
for v := range in {
if n := time.Now(); n.Sub(t) >= aggPeriod {
avg := floats.Sum(buf) / float64(len(buf))
ch <- Decimal(avg)
buf, t = buf[:0], n
}
buf = append(buf, v.Float())
}
if len(buf) > 0 {
avg := floats.Sum(buf) / float64(len(buf))
ch <- Decimal(avg)
}
close(ch)
}()
return ch
}
66 changes: 39 additions & 27 deletions strategy/common.go
Expand Up @@ -23,22 +23,28 @@ type rsi struct {
dir int8
}

func (r *rsi) Update(t *Tick) (buy, sell bool) {
v := t.Price
v = r.rsi.Update(v)
func (s *rsi) Setup(candles []*Candle) {
for _, c := range candles {
s.rsi.Update(c.Close)
}
}

func (s *rsi) Update(c *Candle) (buy, sell bool) {
v := c.Close
v = s.rsi.Update(v)
switch {
case r.idx > 0:
r.idx--
case decimal.Crosover(v, r.last, r.overbought):
r.dir = 1
case decimal.Crossunder(v, r.last, r.oversold):
r.dir = -1
case s.idx > 0:
s.idx--
case decimal.Crosover(v, s.last, s.overbought):
s.dir = 1
case decimal.Crossunder(v, s.last, s.oversold):
s.dir = -1
default:
r.dir = 0
s.dir = 0
}
r.last = v
s.last = v

return r.dir > 0, r.dir < 0
return s.dir > 0, s.dir < 0
}

func MACD(fastPeriod, slowPeriod, signalPeriod int) Strategy {
Expand Down Expand Up @@ -89,24 +95,30 @@ type macd struct {
dir int
}

func (r *macd) Update(t *Tick) (buy, sell bool) {
v := r.macd.Update(t.Price)
func (s *macd) Setup(candles []*Candle) {
for _, c := range candles {
s.macd.Update(c.Close)
}
}

func (s *macd) Update(c *Candle) (buy, sell bool) {
v := s.macd.Update(c.Close)

switch {
case r.idx > 0:
r.idx--
case decimal.Crossunder(v, r.last, 0):
r.dir = -1
case decimal.Crosover(v, r.last, 0):
r.dir = 1
case v > r.last && r.dir > 0:
r.dir++
case v < r.last && r.dir < 0:
r.dir--
case s.idx > 0:
s.idx--
case decimal.Crossunder(v, s.last, 0):
s.dir = -1
case decimal.Crosover(v, s.last, 0):
s.dir = 1
case v > s.last && s.dir > 0:
s.dir++
case v < s.last && s.dir < 0:
s.dir--
default:
r.dir = 0
s.dir = 0
}

r.last = v
return r.dir > r.res, r.dir < -r.res
s.last = v
return s.dir > s.res, s.dir < -s.res
}
27 changes: 19 additions & 8 deletions strategy/helpers.go
Expand Up @@ -19,21 +19,27 @@ type merge struct {
matchAny bool
}

func (m *merge) Update(t *Tick) (buy, sell bool) {
func (s *merge) Setup(candles []*Candle) {
for _, s := range s.strats {
s.Setup(candles)
}
}

func (s *merge) Update(t *Candle) (buy, sell bool) {
buys, sells := 0, 0
for _, s := range m.strats {
buy, sell = s.Update(t)
for _, st := range s.strats {
buy, sell = st.Update(t)
if buy {
buys++
}
if sell {
sells++
}
}
if m.matchAny {
if s.matchAny {
return buys > 0, sells > 0
}
return buys == len(m.strats), sells == len(m.strats)
return buys == len(s.strats), sells == len(s.strats)
}

func Mixed(buyStrat, sellStrat Strategy) Strategy {
Expand All @@ -44,8 +50,13 @@ type mixed struct {
buy, sell Strategy
}

func (m *mixed) Update(t *Tick) (buy, sell bool) {
buy, _ = m.buy.Update(t)
_, sell = m.sell.Update(t)
func (s *mixed) Setup(candles []*Candle) {
s.buy.Setup(candles)
s.sell.Setup(candles)
}

func (s *mixed) Update(t *Candle) (buy, sell bool) {
buy, _ = s.buy.Update(t)
_, sell = s.sell.Update(t)
return
}
28 changes: 16 additions & 12 deletions strategy/strategy.go
Expand Up @@ -14,13 +14,17 @@ type Engine interface {
Stop() (shares int, pricePershare, availableBalance Decimal)
}

type Tick struct {
Price Decimal
type Candle struct {
Open Decimal
High Decimal
Low Decimal
Close Decimal
Volume int
}

type Strategy interface {
Update(*Tick) (buy, sell bool)
Setup(candles []*Candle)
Update(*Candle) (buy, sell bool)
}

type Tx struct {
Expand Down Expand Up @@ -48,10 +52,10 @@ func (t *Tx) PLPerc() Decimal {
}

func ApplySlice(acc Account, str Strategy, symbol string, data *ta.TA) *Tx {
inp := make(chan *Tick, 1)
inp := make(chan *Candle, 1)
go func() {
for i := 0; i < data.Len(); i++ {
inp <- &Tick{Price: data.Get(i)}
inp <- &Candle{Close: data.Get(i)}
}
close(inp)
}()
Expand All @@ -62,7 +66,7 @@ func ApplySlice(acc Account, str Strategy, symbol string, data *ta.TA) *Tx {
return last
}

func Apply(acc Account, str Strategy, symbol string, src <-chan *Tick) <-chan Tx {
func Apply(acc Account, str Strategy, symbol string, src <-chan *Candle) <-chan Tx {
ch := make(chan Tx, len(src))
go func() {
defer close(ch)
Expand All @@ -71,19 +75,19 @@ func Apply(acc Account, str Strategy, symbol string, src <-chan *Tick) <-chan Tx
initial: initial,
Held: acc.Shares(symbol),
}
for t := range src {
shouldBuy, shouldSell := str.Update(t)
for c := range src {
shouldBuy, shouldSell := str.Update(c)
if tx.LastPrice == 0 {
tx.Value = tx.initial + (Decimal(tx.Held) * t.Price)
tx.Value = tx.initial + (Decimal(tx.Held) * c.Close)
}
tx.LastPrice = t.Price
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, t.Price)
shares, pricePerShare := acc.Buy(symbol, c.Close)
if shares == 0 {
continue
}
Expand All @@ -97,7 +101,7 @@ func Apply(acc Account, str Strategy, symbol string, src <-chan *Tick) <-chan Tx
}

if shouldSell {
shares, pricePerShare := acc.Sell(symbol, t.Price)
shares, pricePerShare := acc.Sell(symbol, c.Close)
if shares == 0 {
continue
}
Expand Down
107 changes: 107 additions & 0 deletions studies_01.go
@@ -0,0 +1,107 @@
package ta

// MinMax returns the min/max over a period
// Update returns min
// UpdateAll returns [min, max]
func MinMax(period int) MultiVarStudy {
return &minmax{data: NewCapped(period)}
}

func Min(period int) Study {
return &minmax{data: NewCapped(period)}
}

func Max(period int) Study {
return &minmax{data: NewCapped(period), isMax: true}
}

type minmax struct {
data *TA
isMax bool
}

func (s *minmax) Update(vs ...Decimal) Decimal {
ta := s.data.Append(vs...)
if s.isMax {
return ta.Max()
}
return ta.Min()
}

func (s *minmax) UpdateAll(vs ...Decimal) []Decimal {
ta := s.data.Append(vs...)
return []Decimal{ta.Min(), ta.Max()}
}

func (s *minmax) Len() int { return s.data.Len() }
func (s *minmax) LenAll() []int {
ln := s.Len()
return []int{ln, ln}
}

func (s *minmax) ToStudy() (Study, bool) { return s, true }

func (s *minmax) ToMulti() (MultiVarStudy, bool) { return s, true }

// BBands alias for BollingerBands(period, d, -d, nil)
func BBands(period int) MultiVarStudy {
d := Decimal(period)
return BollingerBands(period, d, d, nil)
}

// BBandsLimits alias for BollingerBands(period, up, down, nil)
func BBandsLimits(period int, up, down Decimal) MultiVarStudy {
return BollingerBands(period, up, down, nil)
}

// BollingerBands returns a Bollinger Bands study, if ma is nil, the mid will be the mean
// Update will return the upper bound
// UpdateAll returns [upper, mid, lower]
func BollingerBands(period int, up, down Decimal, ma MovingAverageFunc) MultiVarStudy {
if down > 0 {
down = -down
}
bb := &bbands{
std: newVar(period, runStd),
up: up,
down: down,
}

if ma != nil {
bb.ext = ma(period)
}

return bb
}

type bbands struct {
ext MovingAverage
std *variance
up Decimal
down Decimal
}

func (s *bbands) Update(vs ...Decimal) Decimal {
return s.UpdateAll(vs...)[0]
}

func (s *bbands) UpdateAll(vs ...Decimal) []Decimal {
var sd, base Decimal
if s.ext == nil {
data := s.std.UpdateAll(vs...)
sd, base = data[1], data[2]
} else {
sd = s.std.Update(vs...)
base = s.ext.Update(vs...)
}
return []Decimal{base + sd*s.up, base, base + sd*s.down}
}

func (s *bbands) Len() int { return s.std.Len() }
func (s *bbands) LenAll() []int {
ln := s.Len()
return []int{ln, ln, ln}
}
func (s *bbands) ToStudy() (Study, bool) { return s, true }

func (s *bbands) ToMulti() (MultiVarStudy, bool) { return s, true }

0 comments on commit d0b59c2

Please sign in to comment.