mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* CI: Bump go version, linters and fix minor issues * Bump version, fix loop variables * Revert * Rid TODOs now that 1.51 has been released
355 lines
12 KiB
Go
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
|
|
}
|