mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-09 15:11:10 +00:00
technical_analysis: TWAP & VWAP + TA methods to candles and link to existing RPC server for GCTCLI prototyping (#970)
* kline: add weighted price helpers for candles * twap/vwap: basic implementation and hook to rpc for protype * ta: cont implementation. (WIP) * kline: Add tests * kline: add helpers * ta: full impl. * kline: remove support for macd and add in correlation-coefficient handling * rpc: change naming convention * linter: fix * protolinter: fix * linter: ++ * kline: reinstate macd handling after adding in check * glorious: nits * gctcl: linter * Update exchanges/kline/weighted_price.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * glorious: nits v2.0 * kline: fix test * huobi-tests: shift from next quarter to this weeks contracts as they were erroring out in tests. * btcmarkets: update supported kline intervals * zb: fix test * rpcserver: fix bug and tests Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
354
exchanges/kline/technical_analysis.go
Normal file
354
exchanges/kline/technical_analysis.go
Normal file
@@ -0,0 +1,354 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/thrasher-corp/gct-ta/indicators"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidPeriod = errors.New("invalid period")
|
||||
errNoData = errors.New("no data")
|
||||
errInvalidDeviationMultiplier = errors.New("invalid deviation multiplier")
|
||||
errNilOHLC = errors.New("nil OHLC data")
|
||||
errInvalidDataSetLengths = errors.New("invalid data set lengths")
|
||||
errNotEnoughData = errors.New("not enough data to derive signal")
|
||||
)
|
||||
|
||||
// OHLC is a connector for technical analysis usage
|
||||
type OHLC struct {
|
||||
Open []float64
|
||||
High []float64
|
||||
Low []float64
|
||||
Close []float64
|
||||
Volume []float64
|
||||
}
|
||||
|
||||
// GetOHLC returns the entire subset of candles as a friendly type for gct
|
||||
// technical analysis usage.
|
||||
func (k *Item) GetOHLC() *OHLC {
|
||||
ohlc := &OHLC{
|
||||
Open: make([]float64, len(k.Candles)),
|
||||
High: make([]float64, len(k.Candles)),
|
||||
Low: make([]float64, len(k.Candles)),
|
||||
Close: make([]float64, len(k.Candles)),
|
||||
Volume: make([]float64, len(k.Candles)),
|
||||
}
|
||||
for x := range k.Candles {
|
||||
ohlc.Open[x] = k.Candles[x].Open
|
||||
ohlc.High[x] = k.Candles[x].High
|
||||
ohlc.Low[x] = k.Candles[x].Low
|
||||
ohlc.Close[x] = k.Candles[x].Close
|
||||
ohlc.Volume[x] = k.Candles[x].Volume
|
||||
}
|
||||
return ohlc
|
||||
}
|
||||
|
||||
// GetAverageTrueRange returns the Average True Range for the given period.
|
||||
func (k *Item) GetAverageTrueRange(period int64) ([]float64, error) {
|
||||
return k.GetOHLC().GetAverageTrueRange(period)
|
||||
}
|
||||
|
||||
// GetAverageTrueRange returns the Average True Range for the given period.
|
||||
func (o *OHLC) GetAverageTrueRange(period int64) ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get average true range %w", errNilOHLC)
|
||||
}
|
||||
if period <= 0 {
|
||||
return nil, fmt.Errorf("get average true range %w", errInvalidPeriod)
|
||||
}
|
||||
if len(o.High) == 0 {
|
||||
return nil, fmt.Errorf("get average true range high %w", errNoData)
|
||||
}
|
||||
if len(o.Low) == 0 {
|
||||
return nil, fmt.Errorf("get average true range low %w", errNoData)
|
||||
}
|
||||
if len(o.Close) == 0 {
|
||||
return nil, fmt.Errorf("get average true range close %w", errNoData)
|
||||
}
|
||||
if int(period) > len(o.Close) {
|
||||
return nil, fmt.Errorf("get average true range close %w exceeds data length, please reduce",
|
||||
errInvalidPeriod)
|
||||
}
|
||||
return indicators.ATR(o.High, o.Low, o.Close, int(period)), nil
|
||||
}
|
||||
|
||||
// GetBollingerBands returns Bollinger Bands for the given period.
|
||||
func (k *Item) GetBollingerBands(period int64, nbDevUp, nbDevDown float64, m indicators.MaType) (*Bollinger, error) {
|
||||
return k.GetOHLC().GetBollingerBands(period, nbDevUp, nbDevDown, m)
|
||||
}
|
||||
|
||||
// Bollinger defines a return type for the bollinger bands
|
||||
type Bollinger struct {
|
||||
Upper []float64
|
||||
Middle []float64
|
||||
Lower []float64
|
||||
}
|
||||
|
||||
// GetBollingerBands returns Bollinger Bands for the given period.
|
||||
func (o *OHLC) GetBollingerBands(period int64, nbDevUp, nbDevDown float64, m indicators.MaType) (*Bollinger, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get bollinger bands %w", errNilOHLC)
|
||||
}
|
||||
if period <= 0 {
|
||||
return nil, fmt.Errorf("get bollinger bands %w", errInvalidPeriod)
|
||||
}
|
||||
if nbDevUp <= 0 {
|
||||
return nil, fmt.Errorf("get bollinger bands %w upper limit", errInvalidDeviationMultiplier)
|
||||
}
|
||||
if nbDevDown <= 0 {
|
||||
return nil, fmt.Errorf("get bollinger bands %w lower limit", errInvalidDeviationMultiplier)
|
||||
}
|
||||
if len(o.Close) == 0 {
|
||||
return nil, fmt.Errorf("get bollinger bands close %w", errNoData)
|
||||
}
|
||||
if int(period) > len(o.Close) { // TODO: Investigate the panic when this protection is removed.
|
||||
return nil, fmt.Errorf("get bollinger bands %w '%v' should not exceed close data length '%v'",
|
||||
errInvalidPeriod, period, len(o.Close))
|
||||
}
|
||||
var bands Bollinger
|
||||
bands.Upper, bands.Middle, bands.Lower = indicators.BBANDS(o.Close,
|
||||
int(period),
|
||||
nbDevUp,
|
||||
nbDevDown,
|
||||
m)
|
||||
return &bands, nil
|
||||
}
|
||||
|
||||
// GetCorrelationCoefficient returns GetCorrelation Coefficient against another
|
||||
// candle data set for the given period.
|
||||
func (k *Item) GetCorrelationCoefficient(other *Item, period int64) ([]float64, error) {
|
||||
return k.GetOHLC().GetCorrelationCoefficient(other.GetOHLC(), period)
|
||||
}
|
||||
|
||||
// GetCorrelationCoefficient returns GetCorrelation Coefficient against another
|
||||
// candle data set for the given period.
|
||||
func (o *OHLC) GetCorrelationCoefficient(other *OHLC, period int64) ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get correlation coefficient %w", errNilOHLC)
|
||||
}
|
||||
if period <= 0 {
|
||||
return nil, fmt.Errorf("get correlation coefficient %w", errInvalidPeriod)
|
||||
}
|
||||
if period == 1 {
|
||||
// TODO: Check correlation calculation.
|
||||
return nil, fmt.Errorf("get correlation coefficient %w using period 1 results in NaN return",
|
||||
errInvalidPeriod)
|
||||
}
|
||||
if other == nil {
|
||||
return nil, fmt.Errorf("get correlation coefficient %w", errNilOHLC)
|
||||
}
|
||||
|
||||
if len(o.Close) == 0 {
|
||||
return nil, fmt.Errorf("get correlation coefficient close %w", errNoData)
|
||||
}
|
||||
if len(other.Close) == 0 {
|
||||
return nil, fmt.Errorf("get correlation coefficient comparison close %w", errNoData)
|
||||
}
|
||||
if int(period) > len(o.Close) || int(period) > len(other.Close) {
|
||||
return nil, fmt.Errorf("get correlation coefficient %w exceeds data length, please reduce",
|
||||
errInvalidPeriod)
|
||||
}
|
||||
if len(o.Close) != len(other.Close) {
|
||||
return nil,
|
||||
fmt.Errorf("get correlation coefficient comparison close %w between data sets",
|
||||
errInvalidDataSetLengths)
|
||||
}
|
||||
return indicators.CorrelationCoefficient(o.Close, other.Close, int(period)), nil
|
||||
}
|
||||
|
||||
// GetSimpleMovingAverageOnClose returns MA the close prices set for the given
|
||||
// period.
|
||||
func (k *Item) GetSimpleMovingAverageOnClose(period int64) ([]float64, error) {
|
||||
ohlc := k.GetOHLC()
|
||||
return ohlc.GetSimpleMovingAverage(ohlc.Close, period)
|
||||
}
|
||||
|
||||
// GetSimpleMovingAverage returns MA for the supplied price set for the given
|
||||
// period.
|
||||
func (o *OHLC) GetSimpleMovingAverage(option []float64, period int64) ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get simple moving average %w", errNilOHLC)
|
||||
}
|
||||
if period <= 0 {
|
||||
return nil, fmt.Errorf("get simple moving average %w", errInvalidPeriod)
|
||||
}
|
||||
if len(option) == 0 {
|
||||
return nil, fmt.Errorf("get simple moving average %w", errNoData)
|
||||
}
|
||||
if int(period) > len(option) {
|
||||
return nil, fmt.Errorf("get simple moving average %w exceeds data length, please reduce",
|
||||
errInvalidPeriod)
|
||||
}
|
||||
return indicators.SMA(option, int(period)), nil
|
||||
}
|
||||
|
||||
// GetExponentialMovingAverageOnClose returns the EMA on the close price set for
|
||||
// the given period.
|
||||
func (k *Item) GetExponentialMovingAverageOnClose(period int64) ([]float64, error) {
|
||||
ohlc := k.GetOHLC()
|
||||
return ohlc.GetExponentialMovingAverage(ohlc.Close, period)
|
||||
}
|
||||
|
||||
// GetExponentialMovingAverage returns the EMA on the supplied price set for the
|
||||
// given period.
|
||||
func (o *OHLC) GetExponentialMovingAverage(option []float64, period int64) ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get exponential moving average %w", errNilOHLC)
|
||||
}
|
||||
if period <= 0 {
|
||||
return nil, fmt.Errorf("get exponential moving average %w", errInvalidPeriod)
|
||||
}
|
||||
if len(option) == 0 {
|
||||
return nil, fmt.Errorf("get exponential moving average %w", errNoData)
|
||||
}
|
||||
if int(period) > len(option) {
|
||||
return nil, fmt.Errorf("get exponential moving average %w exceeds data length, please reduce",
|
||||
errInvalidPeriod)
|
||||
}
|
||||
return indicators.EMA(option, int(period)), nil
|
||||
}
|
||||
|
||||
// MACD defines MACD values
|
||||
type MACD struct {
|
||||
Results []float64
|
||||
SignalVals []float64
|
||||
Histogram []float64
|
||||
}
|
||||
|
||||
// GetMovingAverageConvergenceDivergenceOnClose returns the
|
||||
// MACD (macd, signal period vals, histogram) for the given price
|
||||
// set and the parameters fast, slow signal time periods.
|
||||
func (k *Item) GetMovingAverageConvergenceDivergenceOnClose(fast, slow, signal int64) (*MACD, error) {
|
||||
ohlc := k.GetOHLC()
|
||||
return ohlc.GetMovingAverageConvergenceDivergence(ohlc.Close, fast, slow, signal)
|
||||
}
|
||||
|
||||
// GetMovingAverageConvergenceDivergence returns the
|
||||
// MACD (macd, signal period vals, histogram) for the given price
|
||||
// set and the parameters fast, slow signal time periods.
|
||||
func (o *OHLC) GetMovingAverageConvergenceDivergence(option []float64, fast, slow, signal int64) (*MACD, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get macd %w", errNilOHLC)
|
||||
}
|
||||
if fast <= 0 {
|
||||
return nil, fmt.Errorf("get macd %w fast", errInvalidPeriod)
|
||||
}
|
||||
if slow <= 0 {
|
||||
return nil, fmt.Errorf("get macd %w slow", errInvalidPeriod)
|
||||
}
|
||||
if fast >= slow {
|
||||
return nil, fmt.Errorf("get macd %w fast should not be equal or exceed slow", errInvalidPeriod)
|
||||
}
|
||||
if signal <= 0 {
|
||||
return nil, fmt.Errorf("get macd %w signal", errInvalidPeriod)
|
||||
}
|
||||
if len(option) == 0 {
|
||||
return nil, fmt.Errorf("get macd %w", errNoData)
|
||||
}
|
||||
|
||||
if len(option) < int(slow+signal-2) {
|
||||
return nil, fmt.Errorf("get macd %w %v data points are less than minimum %v length requirement derived from the slow %v and signal %v period subtract two, increase end date or scale down granularity",
|
||||
errNotEnoughData,
|
||||
len(option),
|
||||
slow+signal-2,
|
||||
slow,
|
||||
signal)
|
||||
}
|
||||
var macd MACD
|
||||
macd.Results, macd.SignalVals, macd.Histogram = indicators.MACD(option,
|
||||
int(fast),
|
||||
int(slow),
|
||||
int(signal))
|
||||
return &macd, nil
|
||||
}
|
||||
|
||||
// GetMoneyFlowIndex returns Money Flow Index for the given period.
|
||||
func (k *Item) GetMoneyFlowIndex(period int64) ([]float64, error) {
|
||||
return k.GetOHLC().GetMoneyFlowIndex(period)
|
||||
}
|
||||
|
||||
// GetMoneyFlowIndex returns Money Flow Index for the given period.
|
||||
func (o *OHLC) GetMoneyFlowIndex(period int64) ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get money flow index %w", errNilOHLC)
|
||||
}
|
||||
if period <= 0 {
|
||||
return nil, fmt.Errorf("get money flow index %w", errInvalidPeriod)
|
||||
}
|
||||
highLen := len(o.High)
|
||||
if highLen == 0 {
|
||||
return nil, fmt.Errorf("get money flow index %w for high", errNoData)
|
||||
}
|
||||
lowLen := len(o.Low)
|
||||
if lowLen == 0 {
|
||||
return nil, fmt.Errorf("get money flow index %w for low", errNoData)
|
||||
}
|
||||
closeLen := len(o.Close)
|
||||
if closeLen == 0 {
|
||||
return nil, fmt.Errorf("get money flow index %w for close", errNoData)
|
||||
}
|
||||
volLen := len(o.Volume)
|
||||
if volLen == 0 {
|
||||
return nil, fmt.Errorf("get money flow index %w for volume", errNoData)
|
||||
}
|
||||
if highLen != closeLen || lowLen != closeLen || volLen != closeLen {
|
||||
// TODO: Investigate the panic when this protection is removed.
|
||||
// This is very unstable with incorrect lengths.
|
||||
return nil, fmt.Errorf("get money flow index %w", errInvalidDataSetLengths)
|
||||
}
|
||||
|
||||
if int(period) >= len(o.Close) {
|
||||
// TODO: Investigate the panic when this protection is removed.
|
||||
return nil, fmt.Errorf("get money flow index %w '%v' should not exceed or equal close data length '%v'",
|
||||
errInvalidPeriod, period, len(o.Close))
|
||||
}
|
||||
return indicators.MFI(o.High, o.Low, o.Close, o.Volume, int(period)), nil
|
||||
}
|
||||
|
||||
// GetOnBalanceVolume returns On Balance Volume.
|
||||
func (k *Item) GetOnBalanceVolume() ([]float64, error) {
|
||||
return k.GetOHLC().GetOnBalanceVolume()
|
||||
}
|
||||
|
||||
// GetOnBalanceVolume returns On Balance Volume.
|
||||
func (o *OHLC) GetOnBalanceVolume() ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get on balance volume %w", errNilOHLC)
|
||||
}
|
||||
if len(o.Close) == 0 {
|
||||
return nil, fmt.Errorf("get on balance volume %w for close", errNoData)
|
||||
}
|
||||
if len(o.Volume) == 0 {
|
||||
return nil, fmt.Errorf("get on balance volume %w for volume", errNoData)
|
||||
}
|
||||
return indicators.OBV(o.Close, o.Volume), nil
|
||||
}
|
||||
|
||||
// GetRelativeStrengthIndexOnClose returns the relative strength index from the
|
||||
// given price set and period.
|
||||
func (k *Item) GetRelativeStrengthIndexOnClose(period int64) ([]float64, error) {
|
||||
ohlc := k.GetOHLC()
|
||||
return ohlc.GetRelativeStrengthIndex(ohlc.Close, period)
|
||||
}
|
||||
|
||||
// GetRelativeStrengthIndex returns the relative strength index from the the
|
||||
// given price set and period.
|
||||
func (o *OHLC) GetRelativeStrengthIndex(option []float64, period int64) ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get relative strength index %w", errNilOHLC)
|
||||
}
|
||||
if period <= 1 {
|
||||
return nil, fmt.Errorf("get relative strength index %w cannot be equal or below 1", errInvalidPeriod)
|
||||
}
|
||||
if len(option) <= 2 {
|
||||
// TODO: Check why 2 data points causes panic.
|
||||
return nil, fmt.Errorf("get relative strength index %w, requires atleast 3 data points", errNotEnoughData)
|
||||
}
|
||||
if int(period) > len(option) {
|
||||
return nil, fmt.Errorf("get exponential moving average %w exceeds data length, please reduce",
|
||||
errInvalidPeriod)
|
||||
}
|
||||
return indicators.RSI(option, int(period)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user