first commit

This commit is contained in:
lychiyu 2024-06-25 23:17:36 +08:00
commit c23020eee5
15 changed files with 727 additions and 0 deletions

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# indicator
The trade indicator.
# Indicator Support
| indicator | support
|----------|------|
| EMA | Yes |
| SMA | Yes |
| SMMA | Yes |
| Stoch | No Test|
| StochRSI| Yes|
# Cheers to
Some indicator refer to [Gekko](https://github.com/thrasher-/gocryptotrader)
Some indicator refer to tradingview wiki [StochRSI](https://www.tradingview.com/wiki/Stochastic_RSI_(STOCH_RSI))

52
boll.go Normal file
View File

@ -0,0 +1,52 @@
package indicator
import (
"math"
)
type Boll struct {
*SMA
k int
mid float64
top float64
bottom float64
}
func NewBoll(winLen, k int) *Boll {
b := new(Boll)
b.SMA = NewSMA(winLen)
b.k = k
return b
}
func (b *Boll) Result() float64 {
return b.mid
}
func (b *Boll) Update(price float64) {
b.SMA.Update(price)
b.Cal()
}
func (b *Boll) Cal() {
b.mid = b.SMA.result
var sd float64
for j := 0; j < b.winLen; j++ {
sd += math.Pow(b.prices[j]-b.mid, 2)
}
sd = math.Sqrt(sd/float64(b.winLen)) * float64(b.k)
b.top = b.mid + sd
b.bottom = b.mid - sd
}
func (b *Boll) Top() float64 {
return b.top
}
func (b *Boll) Bottom() float64 {
return b.bottom
}
func (b *Boll) Indicator() map[string]float64 {
return map[string]float64{"result": b.Result(), "top": b.Top(), "bottom": b.Bottom()}
}

17
common.go Normal file
View File

@ -0,0 +1,17 @@
package indicator
type Updater interface {
Update(price float64)
}
type Indicator interface {
Updater
Result() float64
}
type Crosser interface {
Updater
SlowResult() float64
FastResult() float64
}

60
cross_tool.go Normal file
View File

@ -0,0 +1,60 @@
package indicator
type CrossTool struct {
crosser Crosser
fasts [3]float64 // prev fasts
slows [3]float64 // prev slows
}
func NewCrossTool(crosser Crosser) *CrossTool {
ct := new(CrossTool)
ct.crosser = crosser
return ct
}
func (ct *CrossTool) Update(price float64) {
ct.fasts[2] = ct.fasts[1]
ct.fasts[1] = ct.fasts[0]
ct.slows[2] = ct.slows[1]
ct.slows[1] = ct.slows[0]
ct.crosser.Update(price)
ct.fasts[0] = ct.crosser.FastResult()
ct.slows[0] = ct.crosser.SlowResult()
}
func (ct *CrossTool) IsCrossUp() bool {
prevFast, prevSlow := ct.getPrev()
fast, slow := ct.getCurrent()
if prevFast < prevSlow && fast > slow {
// fmt.Println("up:", ct.fasts, ct.slows)
return true
}
return false
}
func (ct *CrossTool) IsCrossDown() bool {
prevFast, prevSlow := ct.getPrev()
fast, slow := ct.getCurrent()
if prevFast > prevSlow && fast < slow {
// fmt.Println("down:", ct.fasts, ct.slows)
return true
}
return false
}
func (ct *CrossTool) getCurrent() (fast, slow float64) {
fast, slow = ct.fasts[0], ct.slows[0]
return
}
func (ct *CrossTool) getPrev() (prevFast, prevSlow float64) {
prevFast = ct.fasts[1]
prevSlow = ct.slows[1]
// if prev fast is same of prev slow,see prev one
if prevFast == prevSlow {
prevFast = ct.fasts[2]
prevSlow = ct.slows[2]
}
return
}

31
ema.go Normal file
View File

@ -0,0 +1,31 @@
package indicator
type EMA struct {
MABase
alpha float64
bFirst bool
}
func NewEMA(winLen int) *EMA {
e := new(EMA)
e.winLen = winLen
e.alpha = 2 / float64((e.winLen + 1))
e.bFirst = true
return e
}
func (e *EMA) Update(price float64) {
if e.bFirst {
e.result = price
e.bFirst = false
}
e.cal(price)
}
// cal
// EMA = alpha * x + (1 - alpha) * EMA[1]
// alpha = 2 / (y + 1)
func (e *EMA) cal(price float64) {
oldResult := e.result
e.result = e.alpha*price + (1-e.alpha)*oldResult
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.qtrade.icu/coin-quant/indicator
go 1.22.0

26
ma_group.go Normal file
View File

@ -0,0 +1,26 @@
package indicator
type MAGroup struct {
fast Indicator
slow Indicator
}
func NewMAGroup(fast, slow Indicator) *MAGroup {
mg := new(MAGroup)
mg.fast = fast
mg.slow = slow
return mg
}
func (mg *MAGroup) Update(price float64) {
mg.fast.Update(price)
mg.slow.Update(price)
}
func (mg *MAGroup) FastResult() float64 {
return mg.fast.Result()
}
func (mg *MAGroup) SlowResult() float64 {
return mg.slow.Result()
}

10
mabase.go Normal file
View File

@ -0,0 +1,10 @@
package indicator
type MABase struct {
winLen int //window length
result float64
}
func (m *MABase) Result() float64 {
return m.result
}

57
macd.go Normal file
View File

@ -0,0 +1,57 @@
package indicator
type MACD struct {
long Indicator
short Indicator
signal Indicator
dif float64
dea float64
result float64
}
// NewMACDWithSMA macd signal line with simple ma
func NewMACDWithSMA(short, long, signal int) *MACD {
ma := new(MACD)
ma.short = NewEMA(short)
ma.long = NewEMA(long)
ma.signal = NewSMA(signal)
return ma
}
func NewMACD(short, long, signal int) *MACD {
ma := new(MACD)
ma.short = NewEMA(short)
ma.long = NewEMA(long)
ma.signal = NewEMA(signal)
return ma
}
func (ma *MACD) Update(price float64) {
ma.long.Update(price)
ma.short.Update(price)
ma.dif = ma.short.Result() - ma.long.Result()
ma.signal.Update(ma.dif)
ma.dea = ma.signal.Result()
ma.result = ma.dif - ma.dea
}
func (ma *MACD) Result() float64 {
return ma.result
}
func (ma *MACD) DIF() float64 {
return ma.dif
}
func (ma *MACD) DEA() float64 {
return ma.dea
}
func (ma *MACD) FastResult() float64 {
return ma.dif
}
func (ma *MACD) SlowResult() float64 {
return ma.dea
}

52
rsi.go Normal file
View File

@ -0,0 +1,52 @@
package indicator
type RSI struct {
winLen int
avgU *SMMA
avgD *SMMA
u float64
d float64
lastClose *float64
rs float64
result float64
}
func NewRSI(winLen int) *RSI {
r := new(RSI)
r.winLen = winLen
r.avgU = NewSMMA(r.winLen)
r.avgD = NewSMMA(r.winLen)
return r
}
func (r *RSI) Update(price float64) {
if r.lastClose == nil {
r.lastClose = &price
return
}
if price > *r.lastClose {
r.u = price - *r.lastClose
r.d = 0
} else {
r.u = 0
r.d = *r.lastClose - price
}
r.avgU.Update(r.u)
r.avgD.Update(r.d)
uResult := r.avgU.Result()
dResult := r.avgD.Result()
r.rs = uResult / dResult
r.result = 100 - (100 / (1 + r.rs))
if dResult == 0 {
if uResult != 0 {
r.result = 100
} else {
r.result = 0
}
}
r.lastClose = &price
}
func (r *RSI) Result() float64 {
return r.result
}

25
sma.go Normal file
View File

@ -0,0 +1,25 @@
package indicator
type SMA struct {
MABase
prices []float64
sum float64
age int
}
func NewSMA(winLen int) *SMA {
s := new(SMA)
s.winLen = winLen
s.prices = make([]float64, s.winLen)
s.age = 0
return s
}
func (s *SMA) Update(price float64) {
s.result = price
tail := s.prices[s.age]
s.prices[s.age] = price
s.sum += price - tail
s.result = s.sum / float64(s.winLen)
s.age = (s.age + 1) % s.winLen
}

33
smma.go Normal file
View File

@ -0,0 +1,33 @@
package indicator
// SMMA Smoothed Moving Average (SMMA)
type SMMA struct {
MABase
sma *SMA
age int
bFirst bool
}
func NewSMMA(winLen int) *SMMA {
sm := new(SMMA)
sm.winLen = winLen
sm.sma = NewSMA(sm.winLen)
sm.bFirst = true
return sm
}
func (sm *SMMA) Update(price float64) {
if sm.bFirst {
nLen := sm.age + 1
if nLen < sm.winLen {
sm.sma.Update(price)
} else if nLen == sm.winLen {
sm.sma.Update(price)
sm.result = sm.sma.Result()
sm.bFirst = false
}
sm.age++
} else {
sm.result = (sm.result*float64(sm.winLen-1) + price) / float64(sm.winLen)
}
}

74
stoch.go Normal file
View File

@ -0,0 +1,74 @@
package indicator
// Stoch just test with StochRSI
type Stoch struct {
winLen int
prices []float64
lowest float64
highest float64
age int
bFirst bool
result float64
kSMA *SMA
dSMA *SMA
}
func NewStoch(winLen, periodK, periodD int) *Stoch {
s := new(Stoch)
s.winLen = winLen
s.prices = make([]float64, s.winLen)
s.bFirst = true
s.kSMA = NewSMA(periodK)
s.dSMA = NewSMA(periodD)
return s
}
func (s *Stoch) Update(price float64) {
if s.bFirst {
s.lowest = price
s.highest = price
s.bFirst = false
return
}
s.prices[s.age] = price
s.age = (s.age + 1) % s.winLen
s.highest = highest(s.prices)
s.lowest = lowest(s.prices)
if s.highest == s.lowest {
return
}
s.result = (100 * (price - s.lowest)) / (s.highest - s.lowest)
s.kSMA.Update(s.result)
s.dSMA.Update(s.kSMA.Result())
}
func (s *Stoch) Result() float64 {
return s.result
}
func (s *Stoch) KResult() float64 {
return s.kSMA.Result()
}
func (s *Stoch) DResult() float64 {
return s.dSMA.Result()
}
func lowest(prices []float64) (ret float64) {
ret = prices[0]
for _, v := range prices {
if v < ret {
ret = v
}
}
return
}
func highest(prices []float64) (ret float64) {
ret = prices[0]
for _, v := range prices {
if v > ret {
ret = v
}
}
return
}

43
stochrsi.go Normal file
View File

@ -0,0 +1,43 @@
package indicator
// StochRSI
// result test with aicoin's bitmex data
// maybe it's difference with different website
type StochRSI struct {
winLen int
r *RSI
st *Stoch
}
func NewStochRSI(winLen, rsiWinLen, k, d int) *StochRSI {
sr := new(StochRSI)
sr.winLen = winLen
sr.r = NewRSI(winLen)
sr.st = NewStoch(rsiWinLen, k, d)
return sr
}
func (sr *StochRSI) Update(price float64) {
sr.r.Update(price)
sr.st.Update(sr.r.Result())
}
func (sr *StochRSI) KResult() float64 {
return sr.st.KResult()
}
func (sr *StochRSI) DResult() float64 {
return sr.st.DResult()
}
func (sr *StochRSI) Result() float64 {
return sr.st.Result()
}
func (sr *StochRSI) FastResult() float64 {
return sr.st.KResult()
}
func (sr *StochRSI) SlowResult() float64 {
return sr.st.DResult()
}

227
tool.go Normal file
View File

@ -0,0 +1,227 @@
package indicator
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
type NewCommonIndicatorFunc func(params ...int) (CommonIndicator, error)
var (
ExtraIndicators = map[string]NewCommonIndicatorFunc{}
)
// CommonIndicator
type CommonIndicator interface {
Indicator
Indicator() map[string]float64
}
func RegisterIndicator(name string, fn NewCommonIndicatorFunc) {
ExtraIndicators[name] = fn
}
type JsonIndicator struct {
CommonIndicator
}
func NewJsonIndicator(m CommonIndicator) *JsonIndicator {
return &JsonIndicator{CommonIndicator: m}
}
func (j *JsonIndicator) MarshalJSON() (buf []byte, err error) {
ret := j.Indicator()
buf, err = json.Marshal(ret)
return
}
func NewCommonIndicator(name string, params ...int) (ind CommonIndicator, err error) {
name = strings.ToUpper(name)
nLen := len(params)
if nLen == 0 {
err = fmt.Errorf("%s params can't be empty", name)
return
}
switch name {
case "EMA":
if nLen >= 2 {
maGroup := NewMAGroup(NewEMA(params[0]), NewEMA(params[1]))
ind = NewMixed(nil, maGroup)
} else {
ema := NewEMA(params[0])
ind = NewMixed(ema, nil)
}
case "MACD":
if nLen < 3 {
err = fmt.Errorf("%s params not enough", name)
} else {
macd := NewMACD(params[0], params[1], params[2])
ind = NewMixed(macd, macd)
}
case "SMAMACD":
if nLen < 3 {
err = fmt.Errorf("%s params not enough", name)
} else {
macd := NewMACDWithSMA(params[0], params[1], params[2])
ind = NewMixed(macd, macd)
}
case "SMA":
if nLen >= 2 {
maGroup := NewMAGroup(NewSMA(params[0]), NewSMA(params[1]))
ind = NewMixed(nil, maGroup)
} else {
sma := NewSMA(params[0])
ind = NewMixed(sma, nil)
}
case "SMMA":
if nLen >= 2 {
maGroup := NewMAGroup(NewSMMA(params[0]), NewSMMA(params[1]))
ind = NewMixed(nil, maGroup)
} else {
smma := NewSMMA(params[0])
ind = NewMixed(smma, nil)
}
case "STOCHRSI":
if nLen < 4 {
err = fmt.Errorf("%s params not enough", name)
} else {
stochRSI := NewStochRSI(params[0], params[1], params[2], params[3])
ind = NewMixed(stochRSI, stochRSI)
}
case "RSI":
if nLen >= 2 {
maGroup := NewMAGroup(NewRSI(params[0]), NewRSI(params[1]))
ind = NewMixed(nil, maGroup)
} else {
rsi := NewRSI(params[0])
ind = NewMixed(rsi, nil)
}
case "BOLL":
if nLen >= 2 {
boll := NewBoll(params[0], params[1])
ind = boll
} else {
err = fmt.Errorf("%s params not enough", name)
}
default:
fn, ok := ExtraIndicators[name]
if !ok {
err = fmt.Errorf("%s indicator not support", name)
} else {
ind, err = fn(params...)
}
}
if err == nil {
ind = NewJsonIndicator(ind)
}
return
}
type Mixed struct {
indicator Indicator
crossIndicator Crosser
isSameOne bool
crossTool *CrossTool
}
func NewMixed(indicator Indicator, crossIndicator Crosser) *Mixed {
m := new(Mixed)
m.indicator = indicator
m.crossIndicator = crossIndicator
if m.crossIndicator != nil {
m.crossTool = NewCrossTool(m.crossIndicator)
}
m.checkSameOne()
return m
}
func (m *Mixed) checkSameOne() {
if reflect.ValueOf(m.indicator) == reflect.ValueOf(m.crossIndicator) {
m.isSameOne = true
} else {
m.isSameOne = false
}
}
func (m *Mixed) Update(price float64) {
if m.crossTool != nil {
m.crossTool.Update(price)
}
if !m.isSameOne && m.indicator != nil {
m.indicator.Update(price)
}
}
func (m *Mixed) FastResult() float64 {
if m.crossIndicator != nil {
return m.crossIndicator.FastResult()
}
return 0
}
func (m *Mixed) SlowResult() float64 {
if m.crossIndicator != nil {
return m.crossIndicator.SlowResult()
}
return 0
}
func (m *Mixed) IsCrossUp() bool {
if m.crossTool == nil {
fmt.Println("cross tool is nil")
return false
}
return m.crossTool.IsCrossUp()
}
func (m *Mixed) IsCrossDown() bool {
if m.crossTool == nil {
return false
}
return m.crossTool.IsCrossDown()
}
func (m *Mixed) Result() float64 {
if m.indicator != nil {
return m.indicator.Result()
}
return 0
}
func (m *Mixed) SupportResult() bool {
if m.indicator != nil {
return true
}
return false
}
func (m *Mixed) SupportSlowFast() bool {
if m.crossIndicator != nil {
return true
}
return false
}
func (m *Mixed) Indicator() map[string]float64 {
ret := make(map[string]float64)
if m.SupportResult() {
ret["result"] = m.Result()
}
if m.SupportSlowFast() {
ret["fast"] = m.FastResult()
ret["slow"] = m.SlowResult()
if m.IsCrossDown() {
ret["crossDown"] = 1
} else {
ret["crossDown"] = 0
}
if m.IsCrossUp() {
ret["crossUp"] = 1
} else {
ret["crossUp"] = 0
}
}
return ret
}