Files
gocryptotrader/exchanges/kline/technical_analysis.go
Adrian Gallagher 7e08e483fb CI: Bump go version, linters and fix minor issues (#1130)
* CI: Bump go version, linters and fix minor issues

* Bump version, fix loop variables

* Revert

* Rid TODOs now that 1.51 has been released
2023-02-03 15:56:59 +11:00

355 lines
12 KiB
Go

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
// 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 at least 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
}