mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-30 23:16:52 +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:
@@ -114,9 +114,15 @@ func (b *BTCMarkets) SetDefaults() {
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.OneMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
kline.OneMonth.Word(): true,
|
||||
},
|
||||
ResultLimit: 1000,
|
||||
},
|
||||
@@ -978,8 +984,25 @@ func (b *BTCMarkets) ValidateCredentials(ctx context.Context, assetType asset.It
|
||||
|
||||
// FormatExchangeKlineInterval returns Interval to exchange formatted string
|
||||
func (b *BTCMarkets) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
if in == kline.OneDay {
|
||||
switch in {
|
||||
case kline.OneMin:
|
||||
return "1m"
|
||||
case kline.FiveMin:
|
||||
return "5m"
|
||||
case kline.FifteenMin:
|
||||
return "15m"
|
||||
case kline.ThirtyMin:
|
||||
return "30m"
|
||||
case kline.OneHour:
|
||||
return "1h"
|
||||
case kline.SixHour:
|
||||
return "6h"
|
||||
case kline.OneDay:
|
||||
return "1d"
|
||||
case kline.OneWeek:
|
||||
return "1w"
|
||||
case kline.OneMonth:
|
||||
return "1mo"
|
||||
}
|
||||
return in.Short()
|
||||
}
|
||||
|
||||
@@ -988,26 +988,22 @@ func (b *Base) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
return strconv.FormatFloat(in.Duration().Seconds(), 'f', 0, 64)
|
||||
}
|
||||
|
||||
// ValidateKline confirms that the requested pair, asset & interval are supported and/or enabled by the requested exchange
|
||||
// ValidateKline confirms that the requested pair, asset & interval are
|
||||
// supported and/or enabled by the requested exchange.
|
||||
func (b *Base) ValidateKline(pair currency.Pair, a asset.Item, interval kline.Interval) error {
|
||||
var errorList []string
|
||||
var err kline.Error
|
||||
if b.CurrencyPairs.IsAssetEnabled(a) != nil {
|
||||
err.Asset = a
|
||||
errorList = append(errorList, "asset not enabled")
|
||||
errorList = append(errorList, fmt.Sprintf("[%s] asset not enabled", a))
|
||||
} else if !b.CurrencyPairs.Pairs[a].Enabled.Contains(pair, true) {
|
||||
err.Pair = pair
|
||||
errorList = append(errorList, "pair not enabled")
|
||||
errorList = append(errorList, fmt.Sprintf("[%s] pair not enabled", pair))
|
||||
}
|
||||
|
||||
if !b.klineIntervalEnabled(interval) {
|
||||
err.Interval = interval
|
||||
errorList = append(errorList, "interval not supported")
|
||||
errorList = append(errorList, fmt.Sprintf("[%s] interval not supported", interval))
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
err.Err = errors.New(strings.Join(errorList, ","))
|
||||
return &err
|
||||
return fmt.Errorf("%w: %v", kline.ErrValidatingParams, strings.Join(errorList, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -37,7 +37,7 @@ const (
|
||||
var (
|
||||
h HUOBI
|
||||
wsSetupRan bool
|
||||
futuresTestPair = currency.NewPair(currency.BTC, currency.NewCode("NQ"))
|
||||
futuresTestPair = currency.NewPair(currency.BTC, currency.NewCode("CW")) // represents this week - NQ (next quarter) is erroring out.
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -138,7 +138,7 @@ func TestFIndexPriceInfo(t *testing.T) {
|
||||
func TestFContractPriceLimitations(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := h.FContractPriceLimitations(context.Background(),
|
||||
"BTC", "next_quarter", currency.EMPTYPAIR)
|
||||
"BTC", "this_week", currency.EMPTYPAIR)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func TestFContractPriceLimitations(t *testing.T) {
|
||||
func TestFContractOpenInterest(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := h.FContractOpenInterest(context.Background(),
|
||||
"BTC", "next_quarter", currency.EMPTYPAIR)
|
||||
"BTC", "this_week", currency.EMPTYPAIR)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func TestFQueryTieredAdjustmentFactor(t *testing.T) {
|
||||
func TestFQueryHisOpenInterest(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := h.FQueryHisOpenInterest(context.Background(),
|
||||
"BTC", "next_quarter", "60min", "cont", 3)
|
||||
"BTC", "this_week", "60min", "cont", 3)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -2732,8 +2732,8 @@ func TestFormatFuturesPair(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r != "BTC_NQ" {
|
||||
t.Errorf("expected BTC_NQ, got %s", r)
|
||||
if r != "BTC_CW" {
|
||||
t.Errorf("expected BTC_CW, got %s", r)
|
||||
}
|
||||
availInstruments, err := h.FetchTradablePairs(context.Background(), asset.Futures)
|
||||
if err != nil {
|
||||
|
||||
@@ -222,7 +222,7 @@ func (k *Item) SortCandlesByTimestamp(desc bool) {
|
||||
})
|
||||
}
|
||||
|
||||
// FormatDates converts all date to UTC time
|
||||
// FormatDates converts all dates to UTC time
|
||||
func (k *Item) FormatDates() {
|
||||
for x := range k.Candles {
|
||||
k.Candles[x].Time = k.Candles[x].Time.UTC()
|
||||
@@ -324,13 +324,15 @@ func TotalCandlesPerInterval(start, end time.Time, interval Interval) (out float
|
||||
return -1
|
||||
}
|
||||
|
||||
var oneYearDurationInNano = float64(OneYear.Duration().Nanoseconds())
|
||||
|
||||
// IntervalsPerYear helps determine the number of intervals in a year
|
||||
// used in CAGR calculation to know the amount of time of an interval in a year
|
||||
func (i *Interval) IntervalsPerYear() float64 {
|
||||
if i.Duration() == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(OneYear.Duration().Nanoseconds()) / float64(i.Duration().Nanoseconds())
|
||||
return oneYearDurationInNano / float64(i.Duration().Nanoseconds())
|
||||
}
|
||||
|
||||
// ConvertToNewInterval allows the scaling of candles to larger candles
|
||||
@@ -557,10 +559,7 @@ func (h *IntervalRangeHolder) createDateSummaryRange(start, end time.Time, hasDa
|
||||
|
||||
// CreateIntervalTime is a simple helper function to set the time twice
|
||||
func CreateIntervalTime(tt time.Time) IntervalTime {
|
||||
return IntervalTime{
|
||||
Time: tt,
|
||||
Ticks: tt.Unix(),
|
||||
}
|
||||
return IntervalTime{Time: tt, Ticks: tt.Unix()}
|
||||
}
|
||||
|
||||
// Equal allows for easier unix comparison
|
||||
|
||||
@@ -262,31 +262,6 @@ func TestDurationToWord(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKlineErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := Error{
|
||||
Interval: OneYear,
|
||||
Pair: currency.NewPair(currency.BTC, currency.AUD),
|
||||
Err: errors.New("hello world"),
|
||||
}
|
||||
|
||||
if v.Interval != OneYear {
|
||||
t.Fatalf("expected OneYear received %v:", v.Interval)
|
||||
}
|
||||
|
||||
if v.Pair != currency.NewPair(currency.BTC, currency.AUD) {
|
||||
t.Fatalf("expected OneYear received %v:", v.Pair)
|
||||
}
|
||||
|
||||
if v.Error() != "hello world" {
|
||||
t.Fatal("expected error return received empty value")
|
||||
}
|
||||
|
||||
if v.Unwrap().Error() != "hello world" {
|
||||
t.Fatal("expected error return received empty value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTotalCandlesPerInterval(t *testing.T) {
|
||||
t.Parallel()
|
||||
start := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
@@ -52,6 +52,10 @@ var (
|
||||
// ErrNotFoundAtTime returned when looking up a candle at a specific time
|
||||
ErrNotFoundAtTime = errors.New("candle not found at time")
|
||||
|
||||
// ErrValidatingParams defines an error when the kline params are either not
|
||||
// enabled or are invalid.
|
||||
ErrValidatingParams = errors.New("kline param(s) are invalid")
|
||||
|
||||
// SupportedIntervals is a list of all supported intervals
|
||||
SupportedIntervals = []Interval{
|
||||
FifteenSecond,
|
||||
@@ -131,24 +135,6 @@ type ExchangeCapabilitiesEnabled struct {
|
||||
// Interval type for kline Interval usage
|
||||
type Interval time.Duration
|
||||
|
||||
// Error struct to hold kline interval errors
|
||||
type Error struct {
|
||||
Asset asset.Item
|
||||
Pair currency.Pair
|
||||
Interval Interval
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error returns short interval unsupported message
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Unwrap returns interval unsupported message
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// IntervalRangeHolder holds the entire range of intervals
|
||||
// and the start end dates of everything
|
||||
type IntervalRangeHolder struct {
|
||||
|
||||
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
|
||||
}
|
||||
429
exchanges/kline/technical_analysis_test.go
Normal file
429
exchanges/kline/technical_analysis_test.go
Normal file
@@ -0,0 +1,429 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetOHLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
if (&Item{Candles: []Candle{{Open: 1337}}}).GetOHLC() == nil {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAverageTrueRange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetAverageTrueRange(0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetAverageTrueRange(0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetAverageTrueRange(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.High = append(ohlc.High, 1337)
|
||||
_, err = ohlc.GetAverageTrueRange(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Low = append(ohlc.Low, 1337)
|
||||
_, err = ohlc.GetAverageTrueRange(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Close = append(ohlc.Close, 1337)
|
||||
_, err = ohlc.GetAverageTrueRange(9)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetAverageTrueRange(1)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{High: 1337, Low: 1337, Close: 1337}}}
|
||||
_, err = wrap.GetAverageTrueRange(1)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBollingerBands(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetBollingerBands(0, 0, 0, 5)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetBollingerBands(0, 0, 0, 5)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetBollingerBands(9, 0, 0, 5)
|
||||
if !errors.Is(err, errInvalidDeviationMultiplier) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidDeviationMultiplier)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetBollingerBands(9, 1, 0, 5)
|
||||
if !errors.Is(err, errInvalidDeviationMultiplier) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidDeviationMultiplier)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetBollingerBands(9, 1, 1, 5)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Close = append(ohlc.Close, 1337, 1337, 1337, 1337, 1337, 1337, 1337, 1337, 1337)
|
||||
_, err = ohlc.GetBollingerBands(10, 1, 1, 5)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetBollingerBands(9, 1, 1, 5)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}}}
|
||||
_, err = wrap.GetBollingerBands(9, 1, 1, 5)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCorrelationCoefficient(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetCorrelationCoefficient(nil, 0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetCorrelationCoefficient(nil, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetCorrelationCoefficient(nil, 1)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetCorrelationCoefficient(nil, 2)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetCorrelationCoefficient(&OHLC{}, 9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Close = append(ohlc.Close, 1337, 1337)
|
||||
|
||||
_, err = ohlc.GetCorrelationCoefficient(&OHLC{}, 9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetCorrelationCoefficient(&OHLC{Close: []float64{1337}}, 2)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
ohlc.Close = append(ohlc.Close, 1337)
|
||||
_, err = ohlc.GetCorrelationCoefficient(&OHLC{Close: []float64{1337, 1337}}, 2)
|
||||
if !errors.Is(err, errInvalidDataSetLengths) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidDataSetLengths)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetCorrelationCoefficient(&OHLC{Close: []float64{1337, 1337, 1337}}, 2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337}, {Close: 1337}, {Close: 1337}}}
|
||||
_, err = wrap.GetCorrelationCoefficient(&Item{Candles: []Candle{{Close: 1337}, {Close: 1337}, {Close: 1337}}}, 2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSimpleMovingAverage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetSimpleMovingAverage(nil, 0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetSimpleMovingAverage(nil, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetSimpleMovingAverage(nil, 9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetSimpleMovingAverage([]float64{1337}, 9)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetSimpleMovingAverage([]float64{1337, 1337}, 2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337}, {Close: 1337}}}
|
||||
_, err = wrap.GetSimpleMovingAverageOnClose(2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExponentialMovingAverage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetExponentialMovingAverage(nil, 0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetExponentialMovingAverage(nil, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetExponentialMovingAverage(nil, 9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetExponentialMovingAverage([]float64{1337}, 9)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetExponentialMovingAverage([]float64{1337, 1337, 1337}, 2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337}, {Close: 1337}, {Close: 1337}}}
|
||||
_, err = wrap.GetExponentialMovingAverageOnClose(2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMovingAverageConvergenceDivergence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetMovingAverageConvergenceDivergence(nil, 0, 0, 0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence(nil, 0, 0, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence(nil, 1, 0, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence(nil, 1, 1, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence(nil, 1, 2, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence(nil, 1, 2, 1)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence([]float64{1337}, 1, 2, 2)
|
||||
if !errors.Is(err, errNotEnoughData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNotEnoughData)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMovingAverageConvergenceDivergence([]float64{1337, 1337, 1337, 1337, 1337, 1337, 1337, 1337}, 1, 2, 1)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}, {Close: 1337}}}
|
||||
_, err = wrap.GetMovingAverageConvergenceDivergenceOnClose(1, 2, 1)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMoneyFlowIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetMoneyFlowIndex(0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetMoneyFlowIndex(0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMoneyFlowIndex(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.High = append(ohlc.High, 1337, 1337, 1337, 1337, 1337, 1337)
|
||||
_, err = ohlc.GetMoneyFlowIndex(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Low = append(ohlc.Low, 1337, 1337, 1337, 1337, 1337, 1337)
|
||||
_, err = ohlc.GetMoneyFlowIndex(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Close = append(ohlc.Close, 1337, 1337, 1337, 1337, 1337, 1337)
|
||||
_, err = ohlc.GetMoneyFlowIndex(9)
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Volume = append(ohlc.Volume, 1337, 1337, 1337, 1337, 1337)
|
||||
_, err = ohlc.GetMoneyFlowIndex(5)
|
||||
if !errors.Is(err, errInvalidDataSetLengths) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidDataSetLengths)
|
||||
}
|
||||
|
||||
ohlc.Volume = append(ohlc.Volume, 1337)
|
||||
_, err = ohlc.GetMoneyFlowIndex(6)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetMoneyFlowIndex(3)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{
|
||||
{Close: 1337, High: 1337, Low: 1337, Volume: 1337},
|
||||
{Close: 1337, High: 1337, Low: 1337, Volume: 1337},
|
||||
{Close: 1337, High: 1337, Low: 1337, Volume: 1337},
|
||||
}}
|
||||
_, err = wrap.GetMoneyFlowIndex(2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOnBalanceVolume(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetOnBalanceVolume()
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetOnBalanceVolume()
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Close = append(ohlc.Close, 1337, 1337, 1337, 1337, 1337, 1337)
|
||||
_, err = ohlc.GetOnBalanceVolume()
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Volume = append(ohlc.Volume, 0.00000001)
|
||||
_, err = ohlc.GetOnBalanceVolume()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337, Volume: 1337}}}
|
||||
_, err = wrap.GetOnBalanceVolume()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRelativeStrengthIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetRelativeStrengthIndex(nil, 0)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetRelativeStrengthIndex(nil, 0)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetRelativeStrengthIndex(nil, 9)
|
||||
if !errors.Is(err, errNotEnoughData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNotEnoughData)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetRelativeStrengthIndex([]float64{1337, 1337, 1337}, 9)
|
||||
if !errors.Is(err, errInvalidPeriod) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidPeriod)
|
||||
}
|
||||
|
||||
wrap := Item{Candles: []Candle{{Close: 1337}, {Close: 1337}, {Close: 1337}}}
|
||||
_, err = wrap.GetRelativeStrengthIndexOnClose(2)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
137
exchanges/kline/weighted_price.go
Normal file
137
exchanges/kline/weighted_price.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidElement = errors.New("invalid element")
|
||||
errElementExceedsDataLength = errors.New("element exceeds data length")
|
||||
errDataLengthMismatch = errors.New("data length mismatch")
|
||||
)
|
||||
|
||||
// GetAveragePrice returns the average price from the open, high, low and close
|
||||
func (c *Candle) GetAveragePrice() float64 {
|
||||
return (c.Open + c.High + c.Low + c.Close) / 4
|
||||
}
|
||||
|
||||
// GetAveragePrice returns the average price from the open, high, low and close
|
||||
func (o *OHLC) GetAveragePrice(element int) (float64, error) {
|
||||
if o == nil {
|
||||
return 0, fmt.Errorf("get average price %w", errNilOHLC)
|
||||
}
|
||||
if element < 0 {
|
||||
return 0, fmt.Errorf("get average price %w", errInvalidElement)
|
||||
}
|
||||
check := element + 1
|
||||
if check > len(o.Open) || check > len(o.High) || check > len(o.Low) || check > len(o.Close) {
|
||||
return 0, fmt.Errorf("get average price %w", errElementExceedsDataLength)
|
||||
}
|
||||
return (o.Open[element] + o.High[element] + o.Low[element] + o.Close[element]) / 4, nil
|
||||
}
|
||||
|
||||
// GetTWAP returns the time weighted average price for the specified period.
|
||||
// NOTE: This assumes the most recent price is at the tail end of the slice.
|
||||
// Based off: https://blog.quantinsti.com/twap/
|
||||
// Only returns one item as all other items are just the average price.
|
||||
func (k *Item) GetTWAP() (float64, error) {
|
||||
if len(k.Candles) == 0 {
|
||||
return 0, fmt.Errorf("get twap %w", errNoData)
|
||||
}
|
||||
var cumAveragePrice float64
|
||||
for x := range k.Candles {
|
||||
cumAveragePrice += k.Candles[x].GetAveragePrice()
|
||||
}
|
||||
return cumAveragePrice / float64(len(k.Candles)), nil
|
||||
}
|
||||
|
||||
// GetTWAP returns the time weighted average price for the specified period.
|
||||
func (o *OHLC) GetTWAP() (float64, error) {
|
||||
if o == nil {
|
||||
return 0, fmt.Errorf("get twap %w", errNilOHLC)
|
||||
}
|
||||
if len(o.Open) == 0 || len(o.High) == 0 || len(o.Low) == 0 || len(o.Close) == 0 {
|
||||
return 0, fmt.Errorf("get twap %w", errNoData)
|
||||
}
|
||||
if len(o.Open) != len(o.High) || len(o.Open) != len(o.Low) || len(o.Open) != len(o.Close) {
|
||||
return 0, fmt.Errorf("get twap %w", errDataLengthMismatch)
|
||||
}
|
||||
|
||||
var cumAveragePrice float64
|
||||
for x := range o.Close {
|
||||
avgPrice, err := o.GetAveragePrice(x)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get twap %w", err)
|
||||
}
|
||||
cumAveragePrice += avgPrice
|
||||
}
|
||||
return cumAveragePrice / float64(len(o.Close)), nil
|
||||
}
|
||||
|
||||
// GetTypicalPrice returns the typical average price from the high, low and
|
||||
// close values.
|
||||
func (c *Candle) GetTypicalPrice() float64 {
|
||||
return (c.High + c.Low + c.Close) / 3
|
||||
}
|
||||
|
||||
// GetTypicalPrice returns the typical average price from the high, low and
|
||||
// close values.
|
||||
func (o *OHLC) GetTypicalPrice(element int) (float64, error) {
|
||||
if o == nil {
|
||||
return 0, fmt.Errorf("get typical price %w", errNilOHLC)
|
||||
}
|
||||
if element < 0 {
|
||||
return 0, fmt.Errorf("get typical price %w", errInvalidElement)
|
||||
}
|
||||
check := element + 1
|
||||
if check > len(o.High) || check > len(o.Low) || check > len(o.Close) {
|
||||
return 0, fmt.Errorf("get typical price %w", errElementExceedsDataLength)
|
||||
}
|
||||
return (o.High[element] + o.Low[element] + o.Close[element]) / 3, nil
|
||||
}
|
||||
|
||||
// GetVWAPs returns the Volume Weighted Averages prices which are the cumulative
|
||||
// average price with respect to the volume.
|
||||
// NOTE: This assumes candles are sorted by time
|
||||
// Based off: https://blog.quantinsti.com/vwap-strategy/
|
||||
func (k *Item) GetVWAPs() ([]float64, error) {
|
||||
if len(k.Candles) == 0 {
|
||||
return nil, fmt.Errorf("get vwap %w", errNoData)
|
||||
}
|
||||
store := make([]float64, len(k.Candles))
|
||||
var cumTotal, cumVolume float64
|
||||
for x := range k.Candles {
|
||||
cumTotal += k.Candles[x].GetTypicalPrice() * k.Candles[x].Volume
|
||||
cumVolume += k.Candles[x].Volume
|
||||
store[x] = cumTotal / cumVolume
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// GetVWAPs returns the Volume Weighted Averages prices which are the cumulative
|
||||
// average price with respect to the volume.
|
||||
func (o *OHLC) GetVWAPs() ([]float64, error) {
|
||||
if o == nil {
|
||||
return nil, fmt.Errorf("get vwap %w", errNilOHLC)
|
||||
}
|
||||
if len(o.Open) == 0 || len(o.High) == 0 || len(o.Low) == 0 || len(o.Close) == 0 {
|
||||
return nil, fmt.Errorf("get vwap %w", errNoData)
|
||||
}
|
||||
if len(o.Open) != len(o.High) || len(o.Open) != len(o.Low) || len(o.Open) != len(o.Close) {
|
||||
return nil, fmt.Errorf("get vwap %w", errDataLengthMismatch)
|
||||
}
|
||||
|
||||
store := make([]float64, len(o.High))
|
||||
var cumTotal, cumVolume float64
|
||||
for x := range o.High {
|
||||
typPrice, err := o.GetTypicalPrice(x)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get vwap %w", err)
|
||||
}
|
||||
cumTotal += typPrice * o.Volume[x]
|
||||
cumVolume += o.Volume[x]
|
||||
store[x] = cumTotal / cumVolume
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
313
exchanges/kline/weighted_price_test.go
Normal file
313
exchanges/kline/weighted_price_test.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package kline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetAveragePrice(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := Candle{}
|
||||
if c.GetAveragePrice() != 0 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
c.High = 20
|
||||
if c.GetAveragePrice() != 5 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAveragePrice_OHLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetAveragePrice(-1)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetAveragePrice(-1)
|
||||
if !errors.Is(err, errInvalidElement) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidElement)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetAveragePrice(0)
|
||||
if !errors.Is(err, errElementExceedsDataLength) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errElementExceedsDataLength)
|
||||
}
|
||||
|
||||
ohlc.High = append(ohlc.High, 20)
|
||||
ohlc.Open = append(ohlc.Open, 0)
|
||||
ohlc.Low = append(ohlc.Low, 0)
|
||||
ohlc.Close = append(ohlc.Close, 0)
|
||||
avgPrice, err := ohlc.GetAveragePrice(0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if avgPrice != 5 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
var twapdataset = []Candle{
|
||||
{Time: time.Date(2020, 6, 17, 0, 0, 0, 0, time.UTC), Close: 351.59, Open: 355.15, High: 355.40, Low: 351.09},
|
||||
{Time: time.Date(2020, 6, 16, 0, 0, 0, 0, time.UTC), Close: 352.08, Open: 351.46, High: 353.20, Low: 344.72},
|
||||
{Time: time.Date(2020, 6, 15, 0, 0, 0, 0, time.UTC), Close: 342.99, Open: 333.25, High: 345.68, Low: 332.58},
|
||||
{Time: time.Date(2020, 6, 12, 0, 0, 0, 0, time.UTC), Close: 338.80, Open: 344.72, High: 347.80, Low: 334.22},
|
||||
{Time: time.Date(2020, 6, 11, 0, 0, 0, 0, time.UTC), Close: 335.90, Open: 349.31, High: 351.06, Low: 335.48},
|
||||
{Time: time.Date(2020, 6, 10, 0, 0, 0, 0, time.UTC), Close: 352.84, Open: 347.90, High: 354.77, Low: 346.09},
|
||||
{Time: time.Date(2020, 6, 9, 0, 0, 0, 0, time.UTC), Close: 343.99, Open: 332.14, High: 345.61, Low: 332.01},
|
||||
{Time: time.Date(2020, 6, 8, 0, 0, 0, 0, time.UTC), Close: 333.46, Open: 330.25, High: 333.60, Low: 327.32},
|
||||
{Time: time.Date(2020, 6, 5, 0, 0, 0, 0, time.UTC), Close: 331.50, Open: 323.35, High: 331.75, Low: 323.23},
|
||||
{Time: time.Date(2020, 6, 4, 0, 0, 0, 0, time.UTC), Close: 322.32, Open: 324.39, High: 325.62, Low: 320.78},
|
||||
{Time: time.Date(2020, 6, 3, 0, 0, 0, 0, time.UTC), Close: 325.12, Open: 324.66, High: 326.20, Low: 322.30},
|
||||
{Time: time.Date(2020, 6, 2, 0, 0, 0, 0, time.UTC), Close: 323.34, Open: 320.75, High: 323.44, Low: 318.93},
|
||||
{Time: time.Date(2020, 6, 1, 0, 0, 0, 0, time.UTC), Close: 321.85, Open: 317.75, High: 322.35, Low: 317.21},
|
||||
{Time: time.Date(2020, 5, 29, 0, 0, 0, 0, time.UTC), Close: 317.94, Open: 319.25, High: 321.15, Low: 316.47},
|
||||
{Time: time.Date(2020, 5, 28, 0, 0, 0, 0, time.UTC), Close: 318.25, Open: 316.77, High: 323.44, Low: 315.63},
|
||||
{Time: time.Date(2020, 5, 27, 0, 0, 0, 0, time.UTC), Close: 318.11, Open: 316.14, High: 318.71, Low: 313.09},
|
||||
{Time: time.Date(2020, 5, 26, 0, 0, 0, 0, time.UTC), Close: 316.73, Open: 323.50, High: 324.24, Low: 316.50},
|
||||
{Time: time.Date(2020, 5, 22, 0, 0, 0, 0, time.UTC), Close: 318.89, Open: 315.77, High: 319.23, Low: 315.35},
|
||||
{Time: time.Date(2020, 5, 21, 0, 0, 0, 0, time.UTC), Close: 316.85, Open: 318.66, High: 320.89, Low: 315.87},
|
||||
{Time: time.Date(2020, 5, 20, 0, 0, 0, 0, time.UTC), Close: 319.23, Open: 316.68, High: 319.52, Low: 316.20},
|
||||
{Time: time.Date(2020, 5, 19, 0, 0, 0, 0, time.UTC), Close: 313.14, Open: 315.03, High: 318.52, Low: 313.01},
|
||||
{Time: time.Date(2020, 5, 18, 0, 0, 0, 0, time.UTC), Close: 314.96, Open: 313.17, High: 316.50, Low: 310.32},
|
||||
}
|
||||
|
||||
func TestGetTWAP_OHLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetTWAP()
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetTWAP()
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Open = append(ohlc.Open, 20)
|
||||
ohlc.High = append(ohlc.High, 20)
|
||||
ohlc.Low = append(ohlc.Low, 20)
|
||||
ohlc.Close = append(ohlc.Close, 20, 20)
|
||||
_, err = ohlc.GetTWAP()
|
||||
if !errors.Is(err, errDataLengthMismatch) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errDataLengthMismatch)
|
||||
}
|
||||
|
||||
i := Item{}
|
||||
i.Candles = twapdataset
|
||||
|
||||
ohlc = i.GetOHLC()
|
||||
twap, err := ohlc.GetTWAP()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if twap != 328.147840909091 {
|
||||
t.Fatal("unexpected value returned from data-set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTWAP(t *testing.T) {
|
||||
t.Parallel()
|
||||
candles := Item{}
|
||||
if _, err := candles.GetTWAP(); !errors.Is(err, errNoData) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
candles.Candles = twapdataset
|
||||
twap, err := candles.GetTWAP()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if twap != 328.147840909091 {
|
||||
t.Fatal("unexpected value returned from data-set")
|
||||
}
|
||||
}
|
||||
|
||||
var vwapdataset = []Candle{
|
||||
{Time: time.Date(2019, 10, 10, 9, 31, 0, 0, time.UTC), Open: 245.2903, High: 245.516, Low: 244.7652, Close: 244.8702, Volume: 103033},
|
||||
{Time: time.Date(2019, 10, 10, 9, 32, 0, 0, time.UTC), Open: 245.0807, High: 245.0807, Low: 244.55, Close: 244.66, Volume: 21168},
|
||||
{Time: time.Date(2019, 10, 10, 9, 33, 0, 0, time.UTC), Open: 244.58, High: 245.8, Low: 244.55, Close: 245.6, Volume: 36544},
|
||||
{Time: time.Date(2019, 10, 10, 9, 34, 0, 0, time.UTC), Open: 245.7097, High: 246.09, Low: 245.57, Close: 245.92, Volume: 30057},
|
||||
{Time: time.Date(2019, 10, 10, 9, 35, 0, 0, time.UTC), Open: 245.62, High: 245.62, Low: 245.62, Close: 245.62, Volume: 26301},
|
||||
{Time: time.Date(2019, 10, 10, 9, 36, 0, 0, time.UTC), Open: 245.7126, High: 246.44, Low: 245.7126, Close: 246.188, Volume: 31494},
|
||||
{Time: time.Date(2019, 10, 10, 9, 37, 0, 0, time.UTC), Open: 246.46, High: 246.46, Low: 246.45, Close: 246.45, Volume: 24271},
|
||||
{Time: time.Date(2019, 10, 10, 9, 38, 0, 0, time.UTC), Open: 246.755, High: 246.755, Low: 246.25, Close: 246.25, Volume: 37951},
|
||||
{Time: time.Date(2019, 10, 10, 9, 39, 0, 0, time.UTC), Open: 246.2818, High: 246.655, Low: 246.2818, Close: 246.655, Volume: 15324},
|
||||
{Time: time.Date(2019, 10, 10, 9, 40, 0, 0, time.UTC), Open: 246.78, High: 246.78, Low: 246.56, Close: 246.762, Volume: 23285},
|
||||
{Time: time.Date(2019, 10, 10, 9, 41, 0, 0, time.UTC), Open: 246.75, High: 246.75, Low: 246.38, Close: 246.5, Volume: 23365},
|
||||
{Time: time.Date(2019, 10, 10, 9, 42, 0, 0, time.UTC), Open: 246.17, High: 246.17, Low: 246.17, Close: 246.17, Volume: 16130},
|
||||
{Time: time.Date(2019, 10, 10, 9, 43, 0, 0, time.UTC), Open: 246.135, High: 246.135, Low: 245.82, Close: 245.82, Volume: 27227},
|
||||
{Time: time.Date(2019, 10, 10, 9, 44, 0, 0, time.UTC), Open: 245.9335, High: 245.9335, Low: 245.91, Close: 245.91, Volume: 14464},
|
||||
{Time: time.Date(2019, 10, 10, 9, 45, 0, 0, time.UTC), Open: 246.41, High: 246.41, Low: 246.41, Close: 246.41, Volume: 17156},
|
||||
{Time: time.Date(2019, 10, 10, 9, 46, 0, 0, time.UTC), Open: 246.44, High: 246.46, Low: 246.1683, Close: 246.1683, Volume: 23938},
|
||||
{Time: time.Date(2019, 10, 10, 9, 47, 0, 0, time.UTC), Open: 246.2857, High: 246.57, Low: 246.2857, Close: 246.57, Volume: 70833},
|
||||
{Time: time.Date(2019, 10, 10, 9, 48, 0, 0, time.UTC), Open: 246.6, High: 247.47, Low: 246.6, Close: 247.47, Volume: 59743},
|
||||
{Time: time.Date(2019, 10, 10, 9, 49, 0, 0, time.UTC), Open: 247.49, High: 247.65, Low: 247.49, Close: 247.65, Volume: 71995},
|
||||
{Time: time.Date(2019, 10, 10, 9, 50, 0, 0, time.UTC), Open: 247.685, High: 247.801, Low: 247.65, Close: 247.69, Volume: 46038},
|
||||
{Time: time.Date(2019, 10, 10, 9, 51, 0, 0, time.UTC), Open: 247.95, High: 248.74, Low: 247.95, Close: 248.74, Volume: 103773},
|
||||
{Time: time.Date(2019, 10, 10, 9, 52, 0, 0, time.UTC), Open: 248.56, High: 248.56, Low: 247.95, Close: 247.95, Volume: 73810},
|
||||
{Time: time.Date(2019, 10, 10, 9, 53, 0, 0, time.UTC), Open: 247.93, High: 247.93, Low: 247.6614, Close: 247.6614, Volume: 29784},
|
||||
{Time: time.Date(2019, 10, 10, 9, 54, 0, 0, time.UTC), Open: 247.74, High: 247.76, Low: 247.65, Close: 247.76, Volume: 37138},
|
||||
{Time: time.Date(2019, 10, 10, 9, 55, 0, 0, time.UTC), Open: 247.93, High: 248.03, Low: 247.93, Close: 248.03, Volume: 53166},
|
||||
{Time: time.Date(2019, 10, 10, 9, 56, 0, 0, time.UTC), Open: 247.91, High: 248.44, Low: 247.91, Close: 248.44, Volume: 40789},
|
||||
{Time: time.Date(2019, 10, 10, 9, 57, 0, 0, time.UTC), Open: 248.52, High: 248.52, Low: 248.3154, Close: 248.3154, Volume: 51988},
|
||||
{Time: time.Date(2019, 10, 10, 9, 58, 0, 0, time.UTC), Open: 248.4409, High: 248.62, Low: 248.4409, Close: 248.62, Volume: 53405},
|
||||
{Time: time.Date(2019, 10, 10, 9, 59, 0, 0, time.UTC), Open: 248.9199, High: 248.9199, Low: 248.9199, Close: 248.9199, Volume: 85348},
|
||||
{Time: time.Date(2019, 10, 10, 10, 0, 0, 0, time.UTC), Open: 248.91, High: 249.08, Low: 248.42, Close: 248.72, Volume: 58270},
|
||||
}
|
||||
|
||||
func TestGetVWAPs(t *testing.T) {
|
||||
t.Parallel()
|
||||
candles := Item{}
|
||||
if _, err := candles.GetVWAPs(); !errors.Is(err, errNoData) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
candles.Candles = vwapdataset
|
||||
vwap, err := candles.GetVWAPs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert(t, vwap[0], 245.05046666666664)
|
||||
assert(t, vwap[1], 245.00156932123465)
|
||||
assert(t, vwap[2], 245.07320400593073)
|
||||
assert(t, vwap[3], 245.19714781780763)
|
||||
assert(t, vwap[4], 245.248374356565)
|
||||
assert(t, vwap[5], 245.35797872352975)
|
||||
assert(t, vwap[6], 245.45540807301208)
|
||||
assert(t, vwap[7], 245.57298124760712)
|
||||
assert(t, vwap[8], 245.61797546720302)
|
||||
assert(t, vwap[9], 245.6901232761351)
|
||||
assert(t, vwap[10], 245.7435986712912)
|
||||
assert(t, vwap[11], 245.76128302894574)
|
||||
assert(t, vwap[12], 245.771994363731)
|
||||
assert(t, vwap[13], 245.7768929849006)
|
||||
assert(t, vwap[14], 245.80115004533573)
|
||||
assert(t, vwap[15], 245.82471633454026)
|
||||
assert(t, vwap[16], 245.90964645148168)
|
||||
assert(t, vwap[17], 246.0356579876492)
|
||||
assert(t, vwap[18], 246.20233204964117)
|
||||
assert(t, vwap[19], 246.29892677543359)
|
||||
assert(t, vwap[20], 246.57315726207088)
|
||||
assert(t, vwap[21], 246.70305234595537)
|
||||
assert(t, vwap[22], 246.73669536160304)
|
||||
assert(t, vwap[23], 246.7746731036053)
|
||||
assert(t, vwap[24], 246.83849361010806)
|
||||
assert(t, vwap[25], 246.89338504378165)
|
||||
assert(t, vwap[26], 246.96313273581723)
|
||||
assert(t, vwap[27], 247.03640100225914)
|
||||
assert(t, vwap[28], 247.16505290840146)
|
||||
assert(t, vwap[29], 247.23522648930867)
|
||||
}
|
||||
|
||||
func TestGetVWAPs_OHLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetVWAPs()
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetVWAPs()
|
||||
if !errors.Is(err, errNoData) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoData)
|
||||
}
|
||||
|
||||
ohlc.Open = append(ohlc.Open, 20)
|
||||
ohlc.High = append(ohlc.High, 20)
|
||||
ohlc.Low = append(ohlc.Low, 20)
|
||||
ohlc.Close = append(ohlc.Close, 20, 20)
|
||||
|
||||
_, err = ohlc.GetVWAPs()
|
||||
if !errors.Is(err, errDataLengthMismatch) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errDataLengthMismatch)
|
||||
}
|
||||
|
||||
ohlc = (&Item{Candles: vwapdataset}).GetOHLC()
|
||||
|
||||
vwap, err := ohlc.GetVWAPs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert(t, vwap[0], 245.05046666666664)
|
||||
assert(t, vwap[1], 245.00156932123465)
|
||||
assert(t, vwap[2], 245.07320400593073)
|
||||
assert(t, vwap[3], 245.19714781780763)
|
||||
assert(t, vwap[4], 245.248374356565)
|
||||
assert(t, vwap[5], 245.35797872352975)
|
||||
assert(t, vwap[6], 245.45540807301208)
|
||||
assert(t, vwap[7], 245.57298124760712)
|
||||
assert(t, vwap[8], 245.61797546720302)
|
||||
assert(t, vwap[9], 245.6901232761351)
|
||||
assert(t, vwap[10], 245.7435986712912)
|
||||
assert(t, vwap[11], 245.76128302894574)
|
||||
assert(t, vwap[12], 245.771994363731)
|
||||
assert(t, vwap[13], 245.7768929849006)
|
||||
assert(t, vwap[14], 245.80115004533573)
|
||||
assert(t, vwap[15], 245.82471633454026)
|
||||
assert(t, vwap[16], 245.90964645148168)
|
||||
assert(t, vwap[17], 246.0356579876492)
|
||||
assert(t, vwap[18], 246.20233204964117)
|
||||
assert(t, vwap[19], 246.29892677543359)
|
||||
assert(t, vwap[20], 246.57315726207088)
|
||||
assert(t, vwap[21], 246.70305234595537)
|
||||
assert(t, vwap[22], 246.73669536160304)
|
||||
assert(t, vwap[23], 246.7746731036053)
|
||||
assert(t, vwap[24], 246.83849361010806)
|
||||
assert(t, vwap[25], 246.89338504378165)
|
||||
assert(t, vwap[26], 246.96313273581723)
|
||||
assert(t, vwap[27], 247.03640100225914)
|
||||
assert(t, vwap[28], 247.16505290840146)
|
||||
assert(t, vwap[29], 247.23522648930867)
|
||||
}
|
||||
|
||||
func TestGetTypicalPrice_OHLC(t *testing.T) {
|
||||
t.Parallel()
|
||||
var ohlc *OHLC
|
||||
_, err := ohlc.GetTypicalPrice(-1)
|
||||
if !errors.Is(err, errNilOHLC) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNilOHLC)
|
||||
}
|
||||
|
||||
ohlc = &OHLC{}
|
||||
_, err = ohlc.GetTypicalPrice(-1)
|
||||
if !errors.Is(err, errInvalidElement) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidElement)
|
||||
}
|
||||
|
||||
_, err = ohlc.GetTypicalPrice(0)
|
||||
if !errors.Is(err, errElementExceedsDataLength) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errElementExceedsDataLength)
|
||||
}
|
||||
|
||||
ohlc.High = append(ohlc.High, 15)
|
||||
ohlc.Low = append(ohlc.Low, 0)
|
||||
ohlc.Close = append(ohlc.Close, 0)
|
||||
avgPrice, err := ohlc.GetTypicalPrice(0)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if avgPrice != 5 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(t *testing.T, received, expected float64) {
|
||||
t.Helper()
|
||||
if received != expected {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", received, expected)
|
||||
}
|
||||
}
|
||||
@@ -989,7 +989,7 @@ func TestValidateCandlesRequest(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = z.validateCandlesRequest(currency.EMPTYPAIR, asset.Spot, time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC), time.Date(2020, 1, 1, 1, 1, 1, 3, time.UTC), kline.OneHour)
|
||||
if err != nil && err.Error() != "pair not enabled" {
|
||||
if !errors.Is(err, kline.ErrValidatingParams) {
|
||||
t.Error(err)
|
||||
}
|
||||
var p currency.Pair
|
||||
|
||||
Reference in New Issue
Block a user