Skip to content

Commit

Permalink
(GCTScript) Add technical analysis support via script bindings (thras…
Browse files Browse the repository at this point in the history
…her-corp#467)

* added mfi and example

* renamed to moving average

* converted to array return type and added obv and mfi

* started work on test coverage

* test coverage added for rsi & mfi

* test coverage added for all indicators removed go mod replace moved to append helper method

* moved all indicators to new appendTo and increased test coverage

* added additional test and bumped go-talib to latest commi

* go.mod update

* linter fixes

* go mod clean up

* small fixes

* reverted changes from previous attempt to rework as data is still incorrect now passing full OHLCV data back to script binding

* testing new structure of passing full ohlcv data

* started linking ohlcv to gctscript

* OHCLV link up completed reworking passing back to indicators started

* OHCLV link up completed reworking passing back to indicators started

* added test coverage for tofloat

* linter fixes (gofmt)

* removed unused value

* improved test coverage

* added correct detection for 1w added ParseInterval test coverage moved OHCLV string to const

* removed unused value

* first round of changes addressed

* all indicators have been split with packages named after each indicator and a new calculate() method added

* linters

* fixed tests

* added check to check ta is running in validator for uploading

* Added test data for OHLCV testing new indicator interface for wrapper

* typed const to float64

* reworked validator data to generate previous timestamps

* rewored macd to return slice of array

* adding bbands linking and example

* why didn't this pick it up before :D

* bumped up total number of modules for test

* moved parseIndicator to exchange added comments

* test coverage added for ParseMAType & ParseIndicatorSelector

* gofmt

* WIP changes

* updated tests for bbands  & obv bumped to latest go-talib

* move multiple use strong to const

* reverted rpc.pb.go to master

* added 4w option

* removed selector from obv as unneeded

* improved test coverage and reworked all indicator methods on how they pass errors back

* order incoming OHCLV data

* revert go.mod

* removed verbose toggles

* added spot asset type

* removed 4w as its unused/uncommon

* renamed

* reworked further tests

* converted all examples to use coinbasepro for consistency

* updated all date ranges to 2019 + 6 months

* backported binance OHLCV wrapper from thrasher-corp#479

* removed o

* rounded numbers

* chnage requests addressed and attempt to fix MACD... today has been really unproctive code wise :D

* Migrated to gct-ta library

* Corrected test import

* wording changes on test

* removed TA lib from go.mod

* PR changes addressed

Removed parallel running from tests due to slight possibility in very extreme cases TestExecution might not be set to the expected value and will cause lower test coverage

* removed pkg folder

* bumped gct-ta version

* gct-ta version bump
  • Loading branch information
xtda authored Apr 24, 2020
1 parent 63635e2 commit 7061527
Show file tree
Hide file tree
Showing 36 changed files with 1,882 additions and 12 deletions.
58 changes: 52 additions & 6 deletions exchanges/binance/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
"github.com/thrasher-corp/gocryptotrader/log"
Expand Down Expand Up @@ -193,7 +194,7 @@ func (b *Binance) GetAggregatedTrades(symbol string, limit int) ([]AggregatedTra
// endTime: endTime filter for the kline data
func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) {
var resp interface{}
var kline []CandleStick
var klineData []CandleStick

params := url.Values{}
params.Set("symbol", arg.Symbol)
Expand All @@ -211,15 +212,20 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) {
path := fmt.Sprintf("%s%s?%s", b.API.Endpoints.URL, candleStick, params.Encode())

if err := b.SendHTTPRequest(path, limitDefault, &resp); err != nil {
return kline, err
return klineData, err
}

for _, responseData := range resp.([]interface{}) {
var candle CandleStick
for i, individualData := range responseData.([]interface{}) {
switch i {
case 0:
candle.OpenTime = individualData.(float64)
tempTime := individualData.(float64)
var err error
candle.OpenTime, err = convert.TimeFromUnixTimestampFloat(tempTime)
if err != nil {
return klineData, err
}
case 1:
candle.Open, _ = strconv.ParseFloat(individualData.(string), 64)
case 2:
Expand All @@ -231,7 +237,12 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) {
case 5:
candle.Volume, _ = strconv.ParseFloat(individualData.(string), 64)
case 6:
candle.CloseTime = individualData.(float64)
tempTime := individualData.(float64)
var err error
candle.CloseTime, err = convert.TimeFromUnixTimestampFloat(tempTime)
if err != nil {
return klineData, err
}
case 7:
candle.QuoteAssetVolume, _ = strconv.ParseFloat(individualData.(string), 64)
case 8:
Expand All @@ -242,9 +253,9 @@ func (b *Binance) GetSpotKline(arg KlinesRequestParams) ([]CandleStick, error) {
candle.TakerBuyQuoteAssetVolume, _ = strconv.ParseFloat(individualData.(string), 64)
}
}
kline = append(kline, candle)
klineData = append(klineData, candle)
}
return kline, nil
return klineData, nil
}

// GetAveragePrice returns current average price for a symbol.
Expand Down Expand Up @@ -728,3 +739,38 @@ func (b *Binance) MaintainWsAuthStreamKey() error {
HTTPRecording: b.HTTPRecording,
})
}

func parseInterval(in time.Duration) (TimeInterval, error) {
switch in {
case kline.OneMin:
return TimeIntervalMinute, nil
case kline.ThreeMin:
return TimeIntervalThreeMinutes, nil
case kline.FiveMin:
return TimeIntervalFiveMinutes, nil
case kline.FifteenMin:
return TimeIntervalFifteenMinutes, nil
case kline.ThirtyMin:
return TimeIntervalThirtyMinutes, nil
case kline.OneHour:
return TimeIntervalHour, nil
case kline.TwoHour:
return TimeIntervalTwoHours, nil
case kline.FourHour:
return TimeIntervalFourHours, nil
case kline.SixHour:
return TimeIntervalSixHours, nil
case kline.OneHour * 8:
return TimeIntervalEightHours, nil
case kline.TwelveHour:
return TimeIntervalTwelveHours, nil
case kline.OneDay:
return TimeIntervalDay, nil
case kline.ThreeDay:
return TimeIntervalThreeDays, nil
case kline.OneWeek:
return TimeIntervalWeek, nil
default:
return TimeIntervalMinute, errInvalidInterval
}
}
132 changes: 132 additions & 0 deletions exchanges/binance/binance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package binance

import (
"testing"
"time"

"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
Expand Down Expand Up @@ -790,3 +792,133 @@ func TestExecutionTypeToOrderStatus(t *testing.T) {
}
}
}

func TestGetHistoricCandles(t *testing.T) {
if mockTests {
t.Skip("skipping test under mock as its covered by GetSpotKlines()")
}
currencyPair := currency.NewPairFromString("BTCUSDT")
start := time.Date(2017, 8, 18, 0, 0, 0, 0, time.UTC)
end := start.AddDate(0, 6, 0)

_, err := b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay)
if err != nil {
t.Fatal(err)
}
}

func TestParseInterval(t *testing.T) {
testCases := []struct {
name string
interval time.Duration
expected TimeInterval
err error
}{
{
"OneMin",
kline.OneMin,
TimeIntervalMinute,
nil,
},
{
"ThreeMin",
kline.ThreeMin,
TimeIntervalThreeMinutes,
nil,
},
{
"FiveMin",
kline.FiveMin,
TimeIntervalFiveMinutes,
nil,
},
{
"FifteenMin",
kline.FifteenMin,
TimeIntervalFifteenMinutes,
nil,
},
{
"ThirtyMin",
kline.ThirtyMin,
TimeIntervalThirtyMinutes,
nil,
},
{
"OneHour",
kline.OneHour,
TimeIntervalHour,
nil,
},
{
"TwoHour",
kline.TwoHour,
TimeIntervalTwoHours,
nil,
},
{
"FourHour",
kline.FourHour,
TimeIntervalFourHours,
nil,
},
{
"SixHour",
kline.SixHour,
TimeIntervalSixHours,
nil,
},
{
"EightHour",
kline.OneHour * 8,
TimeIntervalEightHours,
nil,
},
{
"TwelveHour",
kline.TwelveHour,
TimeIntervalTwelveHours,
nil,
},
{
"OneDay",
kline.OneDay,
TimeIntervalDay,
nil,
},
{
"ThreeDay",
kline.ThreeDay,
TimeIntervalThreeDays,
nil,
},
{
"OneWeek",
kline.OneWeek,
TimeIntervalWeek,
nil,
},
{
"default",
time.Hour * 1337,
TimeIntervalHour,
errInvalidInterval,
},
}

for x := range testCases {
test := testCases[x]
t.Run(test.name, func(t *testing.T) {
v, err := parseInterval(test.interval)
if err != nil {
if err != test.err {
t.Fatal(err)
}
} else {
if v != test.expected {
t.Fatalf("%v: received %v expected %v", test.name, v, test.expected)
}
}
})
}
}
9 changes: 7 additions & 2 deletions exchanges/binance/binance_types.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package binance

import (
"errors"
"time"

"github.com/thrasher-corp/gocryptotrader/currency"
)

var errInvalidInterval = errors.New("invalid interval")

// Response holds basic binance api response data
type Response struct {
Code int `json:"code"`
Expand Down Expand Up @@ -208,13 +213,13 @@ type AggregatedTrade struct {

// CandleStick holds kline data
type CandleStick struct {
OpenTime float64
OpenTime time.Time
Open float64
High float64
Low float64
Close float64
Volume float64
CloseTime float64
CloseTime time.Time
QuoteAssetVolume float64
TradeCount float64
TakerBuyAssetVolume float64
Expand Down
35 changes: 34 additions & 1 deletion exchanges/binance/binance_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,5 +674,38 @@ func (b *Binance) ValidateCredentials() error {

// GetHistoricCandles returns candles between a time period for a set time interval
func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) {
return kline.Item{}, common.ErrNotYetImplemented
intervalToString, err := parseInterval(interval)
if err != nil {
return kline.Item{}, err
}
klineParams := KlinesRequestParams{
Interval: intervalToString,
Symbol: b.FormatExchangeCurrency(pair, a).String(),
StartTime: start.Unix() * 1000,
EndTime: end.Unix() * 1000,
}

candles, err := b.GetSpotKline(klineParams)
if err != nil {
return kline.Item{}, err
}

ret := kline.Item{
Exchange: b.Name,
Pair: pair,
Asset: a,
Interval: interval,
}

for x := range candles {
ret.Candles = append(ret.Candles, kline.Candle{
Time: candles[x].OpenTime,
Open: candles[x].Open,
High: candles[x].Close,
Low: candles[x].Low,
Close: candles[x].Close,
Volume: candles[x].Volume,
})
}
return ret, nil
}
4 changes: 2 additions & 2 deletions exchanges/coinbasepro/coinbasepro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ func TestGetTrades(t *testing.T) {

func TestGetHistoricRatesGranularityCheck(t *testing.T) {
end := time.Now().UTC()
start := end.Add(-time.Second * 300)
start := end.Add(-time.Hour * 24)
p := currency.NewPair(currency.BTC, currency.USD)

_, err := c.GetHistoricCandles(p, asset.Spot, start, end, time.Minute)
_, err := c.GetHistoricCandles(p, asset.Spot, start, end, time.Hour)
if err != nil {
t.Fatal(err)
}
Expand Down
11 changes: 11 additions & 0 deletions gctscript/examples/exchange/ohlcv.gct
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fmt := import("fmt")
exch := import("exchange")
t := import("times")

load := func() {
start := t.add(t.now(), -t.hour*24)
ohlcvData := exch.ohlcv("coinbasepro", "BTC-USD", "-", "SPOT", start, t.now(), "1h")
fmt.println(ohlcvData)
}

load()
15 changes: 15 additions & 0 deletions gctscript/examples/ta/atr.gct
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fmt := import("fmt")
exch := import("exchange")
t := import("times")
atr := import("indicator/atr")

load := func() {
start := t.date(2017, 8 , 17, 0 , 0 , 0, 0)
end := t.add_date(start, 0, 6 , 0)
ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d")

ret := atr.calculate(ohlcvData.candles, 14)
fmt.println(ret)
}

load()
15 changes: 15 additions & 0 deletions gctscript/examples/ta/bbands.gct
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fmt := import("fmt")
exch := import("exchange")
t := import("times")
bbands := import("indicator/bbands")

load := func() {
start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0)
end := t.add_date(start, 0, 6 , 0)
ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d")

ret := bbands.calculate("close", ohlcvData.candles, 20, 2.0, 2.0, "sma")
fmt.println(ret)
}

load()
Loading

0 comments on commit 7061527

Please sign in to comment.