New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add OHLC retrieval func (GetHistoricCandles) to all exchanges and expose it as a wrapper func #414
Add OHLC retrieval func (GetHistoricCandles) to all exchanges and expose it as a wrapper func #414
Conversation
Codecov Report
@@ Coverage Diff @@
## master #414 +/- ##
=========================================
Coverage ? 41.17%
=========================================
Files ? 181
Lines ? 41976
Branches ? 0
=========================================
Hits ? 17282
Misses ? 23251
Partials ? 1443
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome stuff Christian and thank you for your first contribution! I have just a few changes to request
exchanges/binance/binance.go
Outdated
@@ -72,6 +72,10 @@ type Binance struct { | |||
validIntervals []TimeInterval | |||
} | |||
|
|||
func (b *Binance) GetHistoricCandles(pair currency.Pair, rangesize int, granularity int) ([]exchange.Candle, error) { | |||
return nil, common.ErrFunctionNotSupported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an exchange API endpoint is available but is not yet implemented, we use common.ErrFunctionNotImplemented. When an exchange API endpoint isn't available, we use common.ErrFunctionNotSupported. This is used by our exchange_wrapper_coverage and exchange_wrapper_issues tools (https://github.com/thrasher-corp/gocryptotrader/tree/master/cmd/exchange_wrapper_coverage) helping us to know which exchanges support or don't have to support for a particular wrapper func.
The other thing is that we have a segregation between exchange API funcs and wrapper funcs. Wrapper funcs go into the exchanges corresponding _wrapper.go file and API non-wrapper related funcs go into the exchanges main file (binance.go)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation. Indeed it makes sense.
Question on the wrapper funcs, please. I understand GCT aims at providing some kind of unified API across all Exchanges, as far as it is possible.
So the layers are organised as follow:
- rpc.proto defines the GCT Unified API layer
- Then every exchange implements the IBot interface provides access to each Exchange
- the exchange wrapper layer is meant to transcode the data model between the unified API and the exchange peculiar API
Is that correct? I can see there are more details than what I listed, I am keen to understand the purpose of the wrapper layer at this moment. Cheers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GCT unified API is derived from the IBotInterface (https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/interfaces.go#L18). All exchanges have to implement these funcs which gives the engine (and bot) the ability to interact with each exchange through its wrapper funcs. Otherwise, for individual package usage (outside of the engine usage), the application can use any func inside of the exchanges package.
The gRPC server runs as a subsystem via engine, which allows client -> server interaction to cause the engine to perform any actions you'd like on those exchanges (submitting/cancelling orders, getting info etc. all done through the exchange wrappers)
exchanges/coinbene/coinbene.go
Outdated
@@ -25,6 +26,10 @@ type Coinbene struct { | |||
WebsocketConn *wshandler.WebsocketConnection | |||
} | |||
|
|||
func (c *Coinbene) GetHistoricCandles(pair currency2.Pair, rangesize int, granularity int) ([]exchange.Candle, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please place this below the const vars to keep it consistent with all exchange packages
exchanges/lbank/lbank.go
Outdated
@@ -10,11 +10,13 @@ import ( | |||
"encoding/pem" | |||
"errors" | |||
"fmt" | |||
currency2 "github.com/thrasher-corp/gocryptotrader/currency" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can just use currency instead of currency2
go.mod
Outdated
@@ -3,27 +3,45 @@ module github.com/thrasher-corp/gocryptotrader | |||
go 1.12 | |||
|
|||
require ( | |||
cloud.google.com/go v0.51.0 // indirect | |||
cloud.google.com/go/storage v1.5.0 // indirect | |||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20191203043605-d42048ed14fd // indirect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like doing a go get golangci-lint has updated the go.mod/go.sum files, we make this optional and not a requirement as some people would like to compile the binary without the golangci files. The grpc/protobuffs newer versions are fine however
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I am not sure what you mean. I am a newbie in Go, just starting up. Do I need to change anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go modules (and the go.mod file) are go handles dependency management
these additions will cause go get to pull down these package
a common thing people new to go do is run "go get" on optional tools they want to install while inside a go module enabled project
such as
go get -u github.com/golangci/golangci-lint
running this while inside the gct directory will cause it to add golangci-lint and its dependencies to gct's list
easiest thing here would most likely be to revert them both back to master
git checkout master -- go.mod go.sum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gotcha, thanks
I'll fix it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your contribution Christian! I have a few extra requested changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work so far thanks for the PR :D
few minor change requests from me
cmd/gctcli/commands.go
Outdated
|
||
var exchangeName string | ||
if !c.IsSet("exchange") { | ||
exchangeName = c.Args().Get(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will not properly set exchangeName if the command flag --exchange is passed
There are a few ways of handling this two examples:
Setting it manually:
var exchangeName string
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
} else {
exchangeName = c.Args().First()
}
Adding it as a Destination field to command value
Flags: []cli.Flag{
cli.StringFlag{
Name: "start, s",
Usage: "start date to search",
Value: time.Now().Add(-time.Hour).Format(timeFormat),
Destination: &startTime,
},
}
if !c.IsSet("start") {
if c.Args().Get(0) != "" {
startTime = c.Args().Get(0)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using the first option. The second option, using Destination, seems clean but I don't get it so not sure how to use it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Destination flag works like using the Var versions of function from the default flag package where the destination is a pointer
Its useful if you have the same flag across multiple commands (such as exchange name)
you could do
var exchangeName string
Destination: &exchangeName
cmd/gctcli/commands.go
Outdated
} | ||
|
||
var currencyPair string | ||
if !c.IsSet("pair") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above wont set pair if --pair is passed in
cmd/gctcli/commands.go
Outdated
p := currency.NewPairDelimiter(currencyPair, pairDelimiter) | ||
|
||
var rangesize int | ||
if !c.IsSet("rangesize") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above wont set rangesize if --rangesize is passed in
cmd/gctcli/commands.go
Outdated
} | ||
|
||
var granularity int | ||
if !c.IsSet("granularity") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above wont set granularity if --granularity is passed in
cmd/gctcli/commands.go
Outdated
if !c.IsSet("rangesize") { | ||
if c.Args().Get(1) != "" { | ||
rangesizestr, err := strconv.ParseInt(c.Args().Get(2), 10, 32) | ||
if err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also need to handle if the conversion does fail here or we could send invalid characters across
(also made me notice somewhere that i forgot to do this :D)
cmd/gctcli/commands.go
Outdated
if !c.IsSet("granularity") { | ||
if c.Args().Get(2) != "" { | ||
granularitystr, err := strconv.ParseInt(c.Args().Get(3), 10, 32) | ||
if err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also need to handle if the conversion does fail here or we could send invalid characters across
(also made me notice somewhere that i forgot to do this :D)
engine/rpcserver.go
Outdated
return nil, errors.New(errCurrencyPairUnset) | ||
} | ||
|
||
candles, err := GetExchangeByName(req.Exchange).GetHistoricCandles(currency.Pair{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetExchangeByName() can return nil which might cause this to crash if an invalid exchange name is ever passed
gocryptotrader/engine/exchange.go
Lines 74 to 81 in 4e05ad4
func GetExchangeByName(exchName string) exchange.IBotExchange { | |
for x := range Bot.Exchanges { | |
if strings.EqualFold(Bot.Exchanges[x].GetName(), exchName) { | |
return Bot.Exchanges[x] | |
} | |
} | |
return nil | |
} |
While this should never happen due to the name check in the cli its best to also handle it just incase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this actually happened to me: I was trying to call the RPC service from my application and didn't notice I was passing a not existent exchange name. I was getting a weird nil, took me a while to figure out what the issue was as GCT was crashing during unmarshalling and was not able to get to the actual point of failure using the debugger. Eventually,
I just noticed the exchange name was wrong and fixed it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah i was considering at one point changing GetExchangeByName to return an error that the exchange wasn't found as well as pointer to IBotExchange
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignore the approval firefox and github don't play nice sometimes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome PR, thanks, just a couple of nits.
exchanges/coinbene/coinbene.go
Outdated
@@ -5,6 +5,7 @@ import ( | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
currency2 "github.com/thrasher-corp/gocryptotrader/currency" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no package collisions so currency2 can be dropped and called with just currency
exchanges/btcmarkets/btcmarkets.go
Outdated
@@ -82,6 +82,10 @@ type BTCMarkets struct { | |||
WebsocketConn *wshandler.WebsocketConnection | |||
} | |||
|
|||
func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, rangesize int, granularity int) ([]exchange.Candle, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All functions need to have a comment due to linter issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs to be addressed across the code base please.
exchanges/coinbasepro/coinbasepro.go
Outdated
@@ -162,19 +162,28 @@ func (c *CoinbasePro) GetTrades(currencyPair string) ([]Trade, error) { | |||
|
|||
// GetHistoricRates returns historic rates for a product. Rates are returned in | |||
// grouped buckets based on requested granularity. | |||
func (c *CoinbasePro) GetHistoricRates(currencyPair string, start, end, granularity int64) ([]History, error) { | |||
func (c *CoinbasePro) GetHistoricRates(currencyPair string, start, end string, granularity int64) ([]History, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can drop the string after currencyPair
now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
common/common.go
Outdated
@@ -354,3 +355,24 @@ func SplitStringSliceByLimit(in []string, limit uint) [][]string { | |||
} | |||
return sliceSlice | |||
} | |||
|
|||
// InArray checks if _val_ belongs to _array_ | |||
func InArray(val interface{}, array interface{}) (exists bool, index int) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs a test function in common_test.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for those changes! There are only basic linter issues that I have found and I have highlighted each one so you don't miss it. Also a rebase/merge is needed due to our most recent commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All reviewed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK, thanks for addressing those nits!
gctcli.exe gethistoriccandles --exchange=coinbasepro --pair=BTC-USD --rangesize=10 --granularity=86400
{
"candle": [
{
"time": 1579824000,
"low": 8267,
"high": 8400,
"open": 8385.21,
"close": 8302.67,
"volume": 1314.39939822
},
{
"time": 1579737600,
"low": 8278,
"high": 8665,
"open": 8660.38,
"close": 8385.22,
"volume": 12206.24491133
},
{
"time": 1579651200,
"low": 8565,
"high": 8791.76,
"open": 8722.03,
"close": 8660.38,
"volume": 5481.65770829
},
{
"time": 1579564800,
"low": 8465,
"high": 8774.31,
"open": 8628.89,
"close": 8722.03,
"volume": 7520.65103045
},
{
"time": 1579478400,
"low": 8504.06,
"high": 8734.84,
"open": 8697.53,
"close": 8628.89,
"volume": 5605.80561385
},
{
"time": 1579392000,
"low": 8465,
"high": 9194.99,
"open": 8908.95,
"close": 8697.53,
"volume": 15218.51476182
},
{
"time": 1579305600,
"low": 8800.1,
"high": 8980,
"open": 8899.42,
"close": 8909.32,
"volume": 5600.21827844
},
{
"time": 1579219200,
"low": 8665.67,
"high": 9013,
"open": 8714.77,
"close": 8899.42,
"volume": 15447.36512517
},
{
"time": 1579132800,
"low": 8580,
"high": 8850,
"open": 8808.81,
"close": 8714.76,
"volume": 9265.76423506
},
{
"time": 1579046400,
"low": 8550,
"high": 8900,
"open": 8812.58,
"close": 8808.81,
"volume": 14977.61979023
}
]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well done! Tested approved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK! Nice work, thanks for making those changes 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested approved as well with latest changes
…ose it as a wrapper func (thrasher-corp#414) * initial wiring to providegethistoricalcandles * initial wiring to providegethistoricalcandles * initial wiring to providegethistoricalcandles * gethistriccandles work from cli using hard coded inputs * gethistoriccandles RPC service and CLI working fine for coinbasepro * fixed unit test * input check on grpc for gethistoriccandles * updated deps * fixed the return value when a method is not yet implemented * code review: fixed CLI input check and int32->int64 * code review: handling wrong exchange name * added check on granularity and allowing start and end being empty * code review: removed currency2 * code review: dependency reverted * improved func comment * typo in func comment * get historic values tests * unit tests for get historical rates on coinbasepro * using time format time.RFC3339 * names to camel case and improved comments * test cleanup * changed to camel case * added InArray tests * dropped not needed string time * enforced use of int64 * fixed make check * cleaned up code organisation to be consistent * fixed Travis remarks * more Travis remarks * added comments * regenerated proto files after merge * linter fix
PR Description
Added a method to the CLI to pull historic rates (candles) out of an exchange. Only implemented for Coinbasepro.
RPC layer implemented as well by adding method in rpc.proto, rigenerating protobuf files, adding method to coihnbase_wrapper.go
Fixes # (issue)
Fixed GetHistoricRates in coinbasepro.go as the arguments were of the wrong type. Tested and working against Coinbase.
Fixed the linux script to generate protobuf files as it was not workin on mac. For some reason the GOPATH was not resolved.
Type of change
Added funcionality to CLI and RPC
Please delete options that are not relevant and add an
x
in[]
as item is complete.How has this been tested
Tested end to end against coinbase
Checklist