From 2a799e71b20473ad5fce3bf50a230b2c75aaa2b1 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sat, 30 Mar 2019 12:48:43 -0500 Subject: [PATCH 01/26] add ccxt params and exchange headers --- api/exchange.go | 12 +++++ cmd/trade.go | 20 +++++++- examples/configs/trader/sample_mirror.cfg | 12 ++++- examples/configs/trader/sample_trader.cfg | 17 +++++++ plugins/ccxtExchange.go | 6 ++- plugins/ccxtExchange_test.go | 22 +++++---- plugins/factory.go | 19 ++++++-- plugins/mirrorStrategy.go | 38 ++++++++++++++- plugins/sdex.go | 10 ++-- support/sdk/ccxt.go | 58 ++++++++++++++++------- support/sdk/ccxt_test.go | 22 ++++----- trader/config.go | 10 ++++ 12 files changed, 195 insertions(+), 51 deletions(-) diff --git a/api/exchange.go b/api/exchange.go index ee1845921..27db04fdb 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -14,6 +14,18 @@ type ExchangeAPIKey struct { Secret string } +// CcxtParam specifies an additional ccxt parameter +type CcxtParam struct { + Parameter string + Value string +} + +// ExchangeHeader specifies additional HTTP headers for centralized exchanges +type ExchangeHeader struct { + Header string + Value string +} + // Account allows you to access key account functions type Account interface { GetAccountBalances(assetList []interface{}) (map[interface{}]model.Number, error) diff --git a/cmd/trade.go b/cmd/trade.go index 77b725884..8a1a6c713 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -187,8 +187,26 @@ func makeExchangeShimSdex( }) } + l.Infof("makeExchangeShimSdex received exchange headers: %s\n", botConfig.ExchangeHeaders) + + ccxtParams := []api.CcxtParam{} + for _, param := range botConfig.CcxtParams { + ccxtParams = append(ccxtParams, api.CcxtParam{ + Parameter: param.Parameter, + Value: param.Value, + }) + } + + exchangeHeaders := []api.ExchangeHeader{} + for _, header := range botConfig.ExchangeHeaders { + exchangeHeaders = append(exchangeHeaders, api.ExchangeHeader{ + Header: header.Header, + Value: header.Value, + }) + } + var exchangeAPI api.Exchange - exchangeAPI, e = plugins.MakeTradingExchange(botConfig.TradingExchange, exchangeAPIKeys, *options.simMode) + exchangeAPI, e = plugins.MakeTradingExchange(botConfig.TradingExchange, exchangeAPIKeys, ccxtParams, exchangeHeaders, *options.simMode) if e != nil { logger.Fatal(l, fmt.Errorf("unable to make trading exchange: %s", e)) return nil, nil diff --git a/examples/configs/trader/sample_mirror.cfg b/examples/configs/trader/sample_mirror.cfg index 3090a2730..5e735a0fb 100644 --- a/examples/configs/trader/sample_mirror.cfg +++ b/examples/configs/trader/sample_mirror.cfg @@ -47,4 +47,14 @@ MIN_BASE_VOLUME=30.0 # you can use multiple API keys to overcome rate limit concerns #[[EXCHANGE_API_KEYS]] #KEY="" -#SECRET="" \ No newline at end of file +#SECRET="" + +# if your ccxt exchanges requires additional parameters, list them here with the the necessary values +# [[CCXT_PARAMS]] +# PARAMETER="" +# VALUE="" + +# if your exchange requires additional headers, list them here with the the necessary values +# [[EXCHANGE_HEADERS]] +# HEADER="" +# VALUE="" \ No newline at end of file diff --git a/examples/configs/trader/sample_trader.cfg b/examples/configs/trader/sample_trader.cfg index 947b2d7c2..7aeef0825 100644 --- a/examples/configs/trader/sample_trader.cfg +++ b/examples/configs/trader/sample_trader.cfg @@ -99,3 +99,20 @@ MAX_OP_FEE_STROOPS=5000 #[[EXCHANGE_API_KEYS]] #KEY="" #SECRET="" + +# some ccxt exchanges require additional parameters, e.g. coinbase pro requires a "password" +# if that parameter is incorporated into ccxt itself, list it here +#[[CCXT_PARAMS]] +#PARAMETER="" +#VALUE="" +#[[CCXT_PARAMS]] +#PARAMETER="" +#VALUE="" + +# if your exchange requires additional parameters as http headers that are not incorporated into ccxt, list them here +#[[EXCHANGE_HEADERS]] +#HEADER="" +#VALUE="" +#[[EXCHANGE_HEADERS]] +#HEADER="" +#VALUE="" \ No newline at end of file diff --git a/plugins/ccxtExchange.go b/plugins/ccxtExchange.go index 3ea94250c..83dd95d13 100644 --- a/plugins/ccxtExchange.go +++ b/plugins/ccxtExchange.go @@ -24,6 +24,7 @@ type ccxtExchange struct { delimiter string orderConstraints map[model.TradingPair]model.OrderConstraints api *sdk.Ccxt + headers []api.ExchangeHeader simMode bool } @@ -32,6 +33,8 @@ func makeCcxtExchange( exchangeName string, orderConstraintOverrides map[model.TradingPair]model.OrderConstraints, apiKeys []api.ExchangeAPIKey, + ccxtParams []api.CcxtParam, + headers []api.ExchangeHeader, simMode bool, ) (api.Exchange, error) { if len(apiKeys) == 0 { @@ -42,7 +45,7 @@ func makeCcxtExchange( return nil, fmt.Errorf("need exactly 1 ExchangeAPIKey") } - c, e := sdk.MakeInitializedCcxtExchange(exchangeName, apiKeys[0]) + c, e := sdk.MakeInitializedCcxtExchange(exchangeName, apiKeys[0], ccxtParams, headers) if e != nil { return nil, fmt.Errorf("error making a ccxt exchange: %s", e) } @@ -57,6 +60,7 @@ func makeCcxtExchange( orderConstraints: orderConstraintOverrides, api: c, simMode: simMode, + headers: headers, }, nil } diff --git a/plugins/ccxtExchange_test.go b/plugins/ccxtExchange_test.go index a156521cf..1309fe6b0 100644 --- a/plugins/ccxtExchange_test.go +++ b/plugins/ccxtExchange_test.go @@ -14,6 +14,8 @@ import ( var supportedExchanges = []string{"binance"} var emptyAPIKey = api.ExchangeAPIKey{} +var emptyParams = api.CcxtParam{} +var emptyHeader = api.ExchangeHeader{} var supportedTradingExchanges = map[string]api.ExchangeAPIKey{ "binance": {}, } @@ -30,7 +32,7 @@ func TestGetTickerPrice_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -57,7 +59,7 @@ func TestGetOrderBook_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -90,7 +92,7 @@ func TestGetTrades_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -115,7 +117,7 @@ func TestGetTradeHistory_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -173,7 +175,7 @@ func validateTrades(t *testing.T, pair model.TradingPair, trades []model.Trade) func TestGetLatestTradeCursor_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -212,7 +214,7 @@ func TestGetAccountBalances_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -257,7 +259,7 @@ func TestGetOpenOrders_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { for _, pair := range tradingPairs { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -372,7 +374,7 @@ func TestAddOrder_Ccxt(t *testing.T) { }, } { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -422,7 +424,7 @@ func TestCancelOrder_Ccxt(t *testing.T) { }, } { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -473,7 +475,7 @@ func TestGetOrderConstraints_Ccxt_Precision(t *testing.T) { for _, kase := range testCases { t.Run(kase.exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(kase.exchangeName, nil, []api.ExchangeAPIKey{emptyAPIKey}, false) + testCcxtExchange, e := makeCcxtExchange(kase.exchangeName, nil, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } diff --git a/plugins/factory.go b/plugins/factory.go index ca6343ccc..e25a6aa24 100644 --- a/plugins/factory.go +++ b/plugins/factory.go @@ -151,8 +151,10 @@ func Strategies() map[string]StrategyContainer { // exchangeFactoryData is a data container that has all the information needed to make an exchange type exchangeFactoryData struct { - simMode bool - apiKeys []api.ExchangeAPIKey + simMode bool + apiKeys []api.ExchangeAPIKey + ccxtParams []api.CcxtParam + headers []api.ExchangeHeader } // ExchangeContainer contains the exchange factory method along with some metadata @@ -210,10 +212,13 @@ func loadExchanges() { TradeEnabled: true, Tested: tested, makeFn: func(exchangeFactoryData exchangeFactoryData) (api.Exchange, error) { + fmt.Printf("makeFn received exchange headers: %s\n", exchangeFactoryData.headers) return makeCcxtExchange( boundExchangeName, nil, exchangeFactoryData.apiKeys, + exchangeFactoryData.ccxtParams, + exchangeFactoryData.headers, exchangeFactoryData.simMode, ) }, @@ -241,7 +246,7 @@ func MakeExchange(exchangeType string, simMode bool) (api.Exchange, error) { } // MakeTradingExchange is a factory method to make an exchange based on a given type -func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, simMode bool) (api.Exchange, error) { +func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, ccxtParams []api.CcxtParam, headers []api.ExchangeHeader, simMode bool) (api.Exchange, error) { if exchange, ok := getExchanges()[exchangeType]; ok { if !exchange.TradeEnabled { return nil, fmt.Errorf("trading is not enabled on this exchange: %s", exchangeType) @@ -251,9 +256,13 @@ func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, simM return nil, fmt.Errorf("cannot make trading exchange, apiKeys mising") } + fmt.Printf("MakeTradingExchange received exchange headers: %s\n", headers) + x, e := exchange.makeFn(exchangeFactoryData{ - simMode: simMode, - apiKeys: apiKeys, + simMode: simMode, + apiKeys: apiKeys, + ccxtParams: ccxtParams, + headers: headers, }) if e != nil { return nil, fmt.Errorf("error when making the '%s' exchange: %s", exchangeType, e) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index 4454b2eb6..20d7ef6e7 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -28,6 +28,38 @@ func (t *exchangeAPIKeysToml) toExchangeAPIKeys() []api.ExchangeAPIKey { return apiKeys } +type ccxtParamsToml []struct { + Parameter string `valid:"-" toml:"PARAMETER"` + Value string `valid:"-" toml:"VALUE"` +} + +func (c *ccxtParamsToml) toCcxtParams() []api.CcxtParam { + ccxtParams := []api.CcxtParam{} + for _, param := range *c { + ccxtParams = append(ccxtParams, api.CcxtParam{ + Parameter: param.Parameter, + Value: param.Value, + }) + } + return ccxtParams +} + +type exchangeHeadersToml []struct { + Header string `valid:"-" toml:"HEADER"` + Value string `valid:"-" toml:"VALUE"` +} + +func (e *exchangeHeadersToml) toExchangeHeaders() []api.ExchangeHeader { + apiHeaders := []api.ExchangeHeader{} + for _, header := range *e { + apiHeaders = append(apiHeaders, api.ExchangeHeader{ + Header: header.Header, + Value: header.Value, + }) + } + return apiHeaders +} + // mirrorConfig contains the configuration params for this strategy type mirrorConfig struct { Exchange string `valid:"-" toml:"EXCHANGE"` @@ -39,6 +71,8 @@ type mirrorConfig struct { MinBaseVolume float64 `valid:"-" toml:"MIN_BASE_VOLUME"` OffsetTrades bool `valid:"-" toml:"OFFSET_TRADES"` ExchangeAPIKeys exchangeAPIKeysToml `valid:"-" toml:"EXCHANGE_API_KEYS"` + CcxtParams ccxtParamsToml `valid:"-" toml:"CCXT_PARAMS"` + ExchangeHeaders exchangeHeadersToml `valid:"-" toml:"EXCHANGE_HEADERS"` } // String impl. @@ -97,7 +131,9 @@ func makeMirrorStrategy(sdex *SDEX, ieif *IEIF, pair *model.TradingPair, baseAss var e error if config.OffsetTrades { exchangeAPIKeys := config.ExchangeAPIKeys.toExchangeAPIKeys() - exchange, e = MakeTradingExchange(config.Exchange, exchangeAPIKeys, simMode) + ccxtParams := config.CcxtParams.toCcxtParams() + exchangeHeaders := config.ExchangeHeaders.toExchangeHeaders() + exchange, e = MakeTradingExchange(config.Exchange, exchangeAPIKeys, ccxtParams, exchangeHeaders, simMode) if e != nil { return nil, e } diff --git a/plugins/sdex.go b/plugins/sdex.go index c8aba18de..a094af5e5 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -93,11 +93,11 @@ func MakeSDEX( threadTracker: threadTracker, operationalBuffer: operationalBuffer, operationalBufferNonNativePct: operationalBufferNonNativePct, - simMode: simMode, - pair: pair, - assetMap: assetMap, - opFeeStroopsFn: opFeeStroopsFn, - tradingOnSdex: exchangeShim == nil, + simMode: simMode, + pair: pair, + assetMap: assetMap, + opFeeStroopsFn: opFeeStroopsFn, + tradingOnSdex: exchangeShim == nil, } if exchangeShim == nil { diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index f30223044..32d33ae5b 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -24,6 +24,7 @@ type Ccxt struct { exchangeName string instanceName string markets map[string]CcxtMarket + headersMap map[string]string } // CcxtMarket represents the result of a LoadMarkets call @@ -52,7 +53,7 @@ type CcxtMarket struct { const pathExchanges = "/exchanges" // MakeInitializedCcxtExchange constructs an instance of Ccxt that is bound to a specific exchange instance on the CCXT REST server -func MakeInitializedCcxtExchange(exchangeName string, apiKey api.ExchangeAPIKey) (*Ccxt, error) { +func MakeInitializedCcxtExchange(exchangeName string, apiKey api.ExchangeAPIKey, params []api.CcxtParam, headers []api.ExchangeHeader) (*Ccxt, error) { if strings.HasSuffix(ccxtBaseURL, "/") { return nil, fmt.Errorf("invalid format for ccxtBaseURL: %s", ccxtBaseURL) } @@ -67,7 +68,7 @@ func MakeInitializedCcxtExchange(exchangeName string, apiKey api.ExchangeAPIKey) instanceName: instanceName, } - e = c.initialize(apiKey) + e = c.initialize(apiKey, params, headers) if e != nil { return nil, fmt.Errorf("error when initializing Ccxt exchange: %s", e) } @@ -102,7 +103,7 @@ func loadExchangeList() { exchangeList = &output } -func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey) error { +func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey, params []api.CcxtParam, headers []api.ExchangeHeader) error { // validate that exchange name is in the exchange list exchangeListed := false el := GetExchangeList() @@ -125,7 +126,7 @@ func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey) error { // make a new instance if needed if !c.hasInstance(instanceList) { - e = c.newInstance(apiKey) + e = c.newInstance(apiKey, params) if e != nil { return fmt.Errorf("error creating new instance '%s' for exchange '%s': %s", c.instanceName, c.exchangeName, e) } @@ -149,6 +150,12 @@ func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey) error { } c.markets = markets + headersMap := map[string]string{} + for _, header := range headers { + headersMap[header.Header] = header.Value + } + c.headersMap = headersMap + return nil } @@ -182,8 +189,22 @@ func (c *Ccxt) hasInstance(instanceList []string) bool { return false } -func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey) error { - data, e := json.Marshal(&struct { +func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey, params []api.CcxtParam) error { + data := map[string]string{} + + data["id"] = c.instanceName + data["apiKey"] = apiKey.Key + data["secret"] = apiKey.Secret + + // if len(params) > 0 { + for _, param := range params { + data[param.Parameter] = param.Value + } + // } + + jsonData, e := json.Marshal(data) + + staticData, e := json.Marshal(&struct { ID string `json:"id"` APIKey string `json:"apiKey"` Secret string `json:"secret"` @@ -192,12 +213,16 @@ func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey) error { APIKey: apiKey.Key, Secret: apiKey.Secret, }) + + fmt.Printf("static data for ccxt would have been: %s\n", string(staticData)) + fmt.Printf("param including string is: %s\n", string(jsonData)) + if e != nil { return fmt.Errorf("error marshaling instanceName '%s' as ID for exchange '%s': %s", c.instanceName, c.exchangeName, e) } var newInstance map[string]interface{} - e = networking.JSONRequest(c.httpClient, "POST", ccxtBaseURL+pathExchanges+"/"+c.exchangeName, string(data), map[string]string{}, &newInstance, "error") + e = networking.JSONRequest(c.httpClient, "POST", ccxtBaseURL+pathExchanges+"/"+c.exchangeName, string(jsonData), c.headersMap, &newInstance, "error") if e != nil { return fmt.Errorf("error in web request when creating new exchange instance for exchange '%s': %s", c.exchangeName, e) } @@ -214,7 +239,7 @@ func (c *Ccxt) symbolExists(tradingPair string) error { url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var exchangeOutput interface{} - e := networking.JSONRequest(c.httpClient, "GET", url, "", map[string]string{}, &exchangeOutput, "error") + e := networking.JSONRequest(c.httpClient, "GET", url, "", c.headersMap, &exchangeOutput, "error") if e != nil { return fmt.Errorf("error fetching details of exchange instance (exchange=%s, instanceName=%s): %s", c.exchangeName, c.instanceName, e) } @@ -260,7 +285,7 @@ func (c *Ccxt) FetchTicker(tradingPair string) (map[string]interface{}, error) { url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchTicker" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching tickers for trading pair '%s': %s", tradingPair, e) } @@ -300,7 +325,7 @@ func (c *Ccxt) FetchOrderBook(tradingPair string, limit *int) (map[string][]Ccxt url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchOrderBook" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching orderbook for trading pair '%s': %s", tradingPair, e) } @@ -361,7 +386,7 @@ func (c *Ccxt) FetchTrades(tradingPair string) ([]CcxtTrade, error) { url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchTrades" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") output := []CcxtTrade{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching trades for trading pair '%s': %s", tradingPair, e) } @@ -394,7 +419,7 @@ func (c *Ccxt) FetchMyTrades(tradingPair string, limit int, maybeCursorStart int url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchMyTrades" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") output := []CcxtTrade{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching trades for trading pair '%s': %s", tradingPair, e) } @@ -413,7 +438,8 @@ func (c *Ccxt) FetchBalance() (map[string]CcxtBalance, error) { url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchBalance" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - e := networking.JSONRequest(c.httpClient, "POST", url, "", map[string]string{}, &output, "error") + fmt.Printf("ccxt.FetchBalance received headers: %s\n", c.headersMap) + e := networking.JSONRequest(c.httpClient, "POST", url, "", c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching balance: %s", e) } @@ -479,7 +505,7 @@ func (c *Ccxt) FetchOpenOrders(tradingPairs []string) (map[string][]CcxtOpenOrde url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchOpenOrders" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching open orders: %s", e) } @@ -535,7 +561,7 @@ func (c *Ccxt) CreateLimitOrder(tradingPair string, side string, amount float64, url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/createOrder" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error creating order: %s", e) } @@ -574,7 +600,7 @@ func (c *Ccxt) CancelOrder(orderID string, tradingPair string) (*CcxtOpenOrder, url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/cancelOrder" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - e = networking.JSONRequest(c.httpClient, "POST", url, string(data), map[string]string{}, &output, "error") + e = networking.JSONRequest(c.httpClient, "POST", url, string(data), c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error canceling order: %s", e) } diff --git a/support/sdk/ccxt_test.go b/support/sdk/ccxt_test.go index f1bec5f30..51cacbbf6 100644 --- a/support/sdk/ccxt_test.go +++ b/support/sdk/ccxt_test.go @@ -42,7 +42,7 @@ func TestMakeValid(t *testing.T) { return } - _, e := MakeInitializedCcxtExchange("kraken", api.ExchangeAPIKey{}) + _, e := MakeInitializedCcxtExchange("kraken", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("unexpected error: %s", e)) return @@ -55,7 +55,7 @@ func TestMakeInvalid(t *testing.T) { return } - _, e := MakeInitializedCcxtExchange("missing-exchange", api.ExchangeAPIKey{}) + _, e := MakeInitializedCcxtExchange("missing-exchange", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) if e == nil { assert.Fail(t, "expected an error when trying to make and initialize an exchange that is missing: 'missing-exchange'") return @@ -73,7 +73,7 @@ func TestFetchTickers(t *testing.T) { return } - c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}) + c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -94,7 +94,7 @@ func TestFetchTickersWithMissingSymbol(t *testing.T) { return } - c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}) + c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -183,7 +183,7 @@ func runTestFetchOrderBook(k orderbookTest, t *testing.T) { return } - c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -256,7 +256,7 @@ func TestFetchTrades(t *testing.T) { } { tradingPairString := strings.Replace(k.tradingPair, "/", "_", -1) t.Run(fmt.Sprintf("%s-%s", k.exchangeName, tradingPairString), func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -311,7 +311,7 @@ func TestFetchMyTrades(t *testing.T) { } { tradingPairString := strings.Replace(k.tradingPair, "/", "_", -1) t.Run(fmt.Sprintf("%s-%s", k.exchangeName, tradingPairString), func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -388,7 +388,7 @@ func TestFetchBalance(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -436,7 +436,7 @@ func TestOpenOrders(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -519,7 +519,7 @@ func TestCreateLimitOrder(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -604,7 +604,7 @@ func TestCancelOrder(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return diff --git a/trader/config.go b/trader/config.go index 5ec0a68e5..a425938b1 100644 --- a/trader/config.go +++ b/trader/config.go @@ -47,6 +47,14 @@ type BotConfig struct { Key string `valid:"-" toml:"KEY"` Secret string `valid:"-" toml:"SECRET"` } `valid:"-" toml:"EXCHANGE_API_KEYS"` + CcxtParams []struct { + Parameter string `valid:"-" toml:"PARAMETER"` + Value string `valid:"-" toml:"VALUE"` + } `valid:"-" toml:"CCXT_PARAMS"` + ExchangeHeaders []struct { + Header string `valid:"-" toml:"HEADER"` + Value string `valid:"-" toml:"VALUE"` + } `valid:"-" toml:"EXCHANGE_HEADERS"` // initialized later tradingAccount *string @@ -60,6 +68,8 @@ type BotConfig struct { func (b BotConfig) String() string { return utils.StructString(b, map[string]func(interface{}) interface{}{ "EXCHANGE_API_KEYS": utils.Hide, + "CCXT_PARAMS": utils.Hide, + "EXCHANGE_HEADERS": utils.Hide, "SOURCE_SECRET_SEED": utils.SecretKey2PublicKey, "TRADING_SECRET_SEED": utils.SecretKey2PublicKey, "ALERT_API_KEY": utils.Hide, From 1c60a7fe2dc8bd4cc88e4cd96d01467cc977af17 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sun, 31 Mar 2019 11:44:57 -0500 Subject: [PATCH 02/26] remove diagnostic prints --- cmd/trade.go | 2 -- plugins/factory.go | 1 - support/sdk/ccxt.go | 1 - 3 files changed, 4 deletions(-) diff --git a/cmd/trade.go b/cmd/trade.go index 8a1a6c713..06a7ec02d 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -187,8 +187,6 @@ func makeExchangeShimSdex( }) } - l.Infof("makeExchangeShimSdex received exchange headers: %s\n", botConfig.ExchangeHeaders) - ccxtParams := []api.CcxtParam{} for _, param := range botConfig.CcxtParams { ccxtParams = append(ccxtParams, api.CcxtParam{ diff --git a/plugins/factory.go b/plugins/factory.go index e25a6aa24..369e1d0aa 100644 --- a/plugins/factory.go +++ b/plugins/factory.go @@ -212,7 +212,6 @@ func loadExchanges() { TradeEnabled: true, Tested: tested, makeFn: func(exchangeFactoryData exchangeFactoryData) (api.Exchange, error) { - fmt.Printf("makeFn received exchange headers: %s\n", exchangeFactoryData.headers) return makeCcxtExchange( boundExchangeName, nil, diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index 32d33ae5b..01b092ee8 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -438,7 +438,6 @@ func (c *Ccxt) FetchBalance() (map[string]CcxtBalance, error) { url := ccxtBaseURL + pathExchanges + "/" + c.exchangeName + "/" + c.instanceName + "/fetchBalance" // decode generic data (see "https://blog.golang.org/json-and-go#TOC_4.") var output interface{} - fmt.Printf("ccxt.FetchBalance received headers: %s\n", c.headersMap) e := networking.JSONRequest(c.httpClient, "POST", url, "", c.headersMap, &output, "error") if e != nil { return nil, fmt.Errorf("error fetching balance: %s", e) From 86930f98582e6d66ece55e27bc1e04f9a7cfab8e Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sun, 31 Mar 2019 12:00:20 -0500 Subject: [PATCH 03/26] update ccxt_test --- support/sdk/ccxt.go | 15 --------------- support/sdk/ccxt_test.go | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index 01b092ee8..a6a4d4848 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -196,27 +196,12 @@ func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey, params []api.CcxtParam) er data["apiKey"] = apiKey.Key data["secret"] = apiKey.Secret - // if len(params) > 0 { for _, param := range params { data[param.Parameter] = param.Value } - // } jsonData, e := json.Marshal(data) - staticData, e := json.Marshal(&struct { - ID string `json:"id"` - APIKey string `json:"apiKey"` - Secret string `json:"secret"` - }{ - ID: c.instanceName, - APIKey: apiKey.Key, - Secret: apiKey.Secret, - }) - - fmt.Printf("static data for ccxt would have been: %s\n", string(staticData)) - fmt.Printf("param including string is: %s\n", string(jsonData)) - if e != nil { return fmt.Errorf("error marshaling instanceName '%s' as ID for exchange '%s': %s", c.instanceName, c.exchangeName, e) } diff --git a/support/sdk/ccxt_test.go b/support/sdk/ccxt_test.go index 51cacbbf6..035fb93c9 100644 --- a/support/sdk/ccxt_test.go +++ b/support/sdk/ccxt_test.go @@ -42,7 +42,7 @@ func TestMakeValid(t *testing.T) { return } - _, e := MakeInitializedCcxtExchange("kraken", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) + _, e := MakeInitializedCcxtExchange("kraken", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("unexpected error: %s", e)) return @@ -55,7 +55,7 @@ func TestMakeInvalid(t *testing.T) { return } - _, e := MakeInitializedCcxtExchange("missing-exchange", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) + _, e := MakeInitializedCcxtExchange("missing-exchange", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) if e == nil { assert.Fail(t, "expected an error when trying to make and initialize an exchange that is missing: 'missing-exchange'") return @@ -73,7 +73,7 @@ func TestFetchTickers(t *testing.T) { return } - c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -94,7 +94,7 @@ func TestFetchTickersWithMissingSymbol(t *testing.T) { return } - c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -183,7 +183,7 @@ func runTestFetchOrderBook(k orderbookTest, t *testing.T) { return } - c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -256,7 +256,7 @@ func TestFetchTrades(t *testing.T) { } { tradingPairString := strings.Replace(k.tradingPair, "/", "_", -1) t.Run(fmt.Sprintf("%s-%s", k.exchangeName, tradingPairString), func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -311,7 +311,7 @@ func TestFetchMyTrades(t *testing.T) { } { tradingPairString := strings.Replace(k.tradingPair, "/", "_", -1) t.Run(fmt.Sprintf("%s-%s", k.exchangeName, tradingPairString), func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -388,7 +388,7 @@ func TestFetchBalance(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -436,7 +436,7 @@ func TestOpenOrders(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -519,7 +519,7 @@ func TestCreateLimitOrder(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -604,7 +604,7 @@ func TestCancelOrder(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return From 0a7626a2f1b80d472195aaf3a581833935096ff0 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sun, 31 Mar 2019 23:49:04 -0500 Subject: [PATCH 04/26] update naming conventions --- api/exchange.go | 6 ++--- cmd/trade.go | 8 +++--- examples/configs/trader/sample_mirror.cfg | 4 +-- examples/configs/trader/sample_trader.cfg | 4 +-- plugins/ccxtExchange.go | 6 ++--- plugins/ccxtExchange_test.go | 32 ++++++++++++++--------- plugins/factory.go | 20 +++++++------- plugins/mirrorStrategy.go | 16 ++++++------ support/sdk/ccxt.go | 6 ++--- support/sdk/ccxt_test.go | 22 ++++++++-------- 10 files changed, 65 insertions(+), 59 deletions(-) diff --git a/api/exchange.go b/api/exchange.go index 27db04fdb..ba9c5deff 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -14,13 +14,13 @@ type ExchangeAPIKey struct { Secret string } -// CcxtParam specifies an additional ccxt parameter -type CcxtParam struct { +// ExchangeParam specifies an additional ccxt parameter +type ExchangeParam struct { Parameter string Value string } -// ExchangeHeader specifies additional HTTP headers for centralized exchanges +// ExchangeHeader specifies additional HTTP headers type ExchangeHeader struct { Header string Value string diff --git a/cmd/trade.go b/cmd/trade.go index 06a7ec02d..aaea9fb12 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -187,9 +187,9 @@ func makeExchangeShimSdex( }) } - ccxtParams := []api.CcxtParam{} - for _, param := range botConfig.CcxtParams { - ccxtParams = append(ccxtParams, api.CcxtParam{ + exchangeParams := []api.ExchangeParam{} + for _, param := range botConfig.ExchangeParams { + exchangeParams = append(exchangeParams, api.ExchangeParam{ Parameter: param.Parameter, Value: param.Value, }) @@ -204,7 +204,7 @@ func makeExchangeShimSdex( } var exchangeAPI api.Exchange - exchangeAPI, e = plugins.MakeTradingExchange(botConfig.TradingExchange, exchangeAPIKeys, ccxtParams, exchangeHeaders, *options.simMode) + exchangeAPI, e = plugins.MakeTradingExchange(botConfig.TradingExchange, exchangeAPIKeys, exchangeParams, exchangeHeaders, *options.simMode) if e != nil { logger.Fatal(l, fmt.Errorf("unable to make trading exchange: %s", e)) return nil, nil diff --git a/examples/configs/trader/sample_mirror.cfg b/examples/configs/trader/sample_mirror.cfg index 5e735a0fb..8858b9378 100644 --- a/examples/configs/trader/sample_mirror.cfg +++ b/examples/configs/trader/sample_mirror.cfg @@ -49,8 +49,8 @@ MIN_BASE_VOLUME=30.0 #KEY="" #SECRET="" -# if your ccxt exchanges requires additional parameters, list them here with the the necessary values -# [[CCXT_PARAMS]] +# if your ccxt exchange requires additional parameters, list them here with the the necessary values +# [[EXCHANGE_PARAMS]] # PARAMETER="" # VALUE="" diff --git a/examples/configs/trader/sample_trader.cfg b/examples/configs/trader/sample_trader.cfg index 7aeef0825..3aa91a7c7 100644 --- a/examples/configs/trader/sample_trader.cfg +++ b/examples/configs/trader/sample_trader.cfg @@ -102,10 +102,10 @@ MAX_OP_FEE_STROOPS=5000 # some ccxt exchanges require additional parameters, e.g. coinbase pro requires a "password" # if that parameter is incorporated into ccxt itself, list it here -#[[CCXT_PARAMS]] +#[[EXCHANGE_PARAMS]] #PARAMETER="" #VALUE="" -#[[CCXT_PARAMS]] +#[[EXCHANGE_PARAMS]] #PARAMETER="" #VALUE="" diff --git a/plugins/ccxtExchange.go b/plugins/ccxtExchange.go index 83dd95d13..3d1066f21 100644 --- a/plugins/ccxtExchange.go +++ b/plugins/ccxtExchange.go @@ -24,7 +24,6 @@ type ccxtExchange struct { delimiter string orderConstraints map[model.TradingPair]model.OrderConstraints api *sdk.Ccxt - headers []api.ExchangeHeader simMode bool } @@ -33,7 +32,7 @@ func makeCcxtExchange( exchangeName string, orderConstraintOverrides map[model.TradingPair]model.OrderConstraints, apiKeys []api.ExchangeAPIKey, - ccxtParams []api.CcxtParam, + exchangeParams []api.ExchangeParam, headers []api.ExchangeHeader, simMode bool, ) (api.Exchange, error) { @@ -45,7 +44,7 @@ func makeCcxtExchange( return nil, fmt.Errorf("need exactly 1 ExchangeAPIKey") } - c, e := sdk.MakeInitializedCcxtExchange(exchangeName, apiKeys[0], ccxtParams, headers) + c, e := sdk.MakeInitializedCcxtExchange(exchangeName, apiKeys[0], exchangeParams, headers) if e != nil { return nil, fmt.Errorf("error making a ccxt exchange: %s", e) } @@ -60,7 +59,6 @@ func makeCcxtExchange( orderConstraints: orderConstraintOverrides, api: c, simMode: simMode, - headers: headers, }, nil } diff --git a/plugins/ccxtExchange_test.go b/plugins/ccxtExchange_test.go index 1309fe6b0..522366fa1 100644 --- a/plugins/ccxtExchange_test.go +++ b/plugins/ccxtExchange_test.go @@ -14,15 +14,17 @@ import ( var supportedExchanges = []string{"binance"} var emptyAPIKey = api.ExchangeAPIKey{} -var emptyParams = api.CcxtParam{} +var emptyParams = api.ExchangeParam{} var emptyHeader = api.ExchangeHeader{} var supportedTradingExchanges = map[string]api.ExchangeAPIKey{ - "binance": {}, + "binance": {}, + "coinbasepro": {}, } var testOrderConstraints = map[model.TradingPair]model.OrderConstraints{ *model.MakeTradingPair(model.XLM, model.USDT): *model.MakeOrderConstraints(4, 5, 0.1), *model.MakeTradingPair(model.XLM, model.BTC): *model.MakeOrderConstraints(8, 4, 1.0), + *model.MakeTradingPair(model.XLM, model.USD): *model.MakeOrderConstraints(6, 0, 1.0), } func TestGetTickerPrice_Ccxt(t *testing.T) { @@ -32,7 +34,7 @@ func TestGetTickerPrice_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -59,7 +61,7 @@ func TestGetOrderBook_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -92,7 +94,7 @@ func TestGetTrades_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -117,7 +119,7 @@ func TestGetTradeHistory_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -175,7 +177,7 @@ func validateTrades(t *testing.T, pair model.TradingPair, trades []model.Trade) func TestGetLatestTradeCursor_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -214,7 +216,7 @@ func TestGetAccountBalances_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -259,7 +261,7 @@ func TestGetOpenOrders_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { for _, pair := range tradingPairs { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -374,7 +376,7 @@ func TestAddOrder_Ccxt(t *testing.T) { }, } { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -424,7 +426,7 @@ func TestCancelOrder_Ccxt(t *testing.T) { }, } { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } @@ -471,11 +473,17 @@ func TestGetOrderConstraints_Ccxt_Precision(t *testing.T) { wantPricePrecision: 8, wantVolPrecision: 0, }, + { + exchangeName: "coinbasepro", + pair: &model.TradingPair{Base: model.XLM, Quote: model.USD}, + wantPricePrecision: 6, + wantVolPrecision: 0, + }, } for _, kase := range testCases { t.Run(kase.exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(kase.exchangeName, nil, []api.ExchangeAPIKey{emptyAPIKey}, []api.CcxtParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(kase.exchangeName, nil, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) if !assert.NoError(t, e) { return } diff --git a/plugins/factory.go b/plugins/factory.go index 369e1d0aa..99a8dcedd 100644 --- a/plugins/factory.go +++ b/plugins/factory.go @@ -151,10 +151,10 @@ func Strategies() map[string]StrategyContainer { // exchangeFactoryData is a data container that has all the information needed to make an exchange type exchangeFactoryData struct { - simMode bool - apiKeys []api.ExchangeAPIKey - ccxtParams []api.CcxtParam - headers []api.ExchangeHeader + simMode bool + apiKeys []api.ExchangeAPIKey + exchangeParams []api.ExchangeParam + headers []api.ExchangeHeader } // ExchangeContainer contains the exchange factory method along with some metadata @@ -216,7 +216,7 @@ func loadExchanges() { boundExchangeName, nil, exchangeFactoryData.apiKeys, - exchangeFactoryData.ccxtParams, + exchangeFactoryData.exchangeParams, exchangeFactoryData.headers, exchangeFactoryData.simMode, ) @@ -245,7 +245,7 @@ func MakeExchange(exchangeType string, simMode bool) (api.Exchange, error) { } // MakeTradingExchange is a factory method to make an exchange based on a given type -func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, ccxtParams []api.CcxtParam, headers []api.ExchangeHeader, simMode bool) (api.Exchange, error) { +func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, exchangeParams []api.ExchangeParam, headers []api.ExchangeHeader, simMode bool) (api.Exchange, error) { if exchange, ok := getExchanges()[exchangeType]; ok { if !exchange.TradeEnabled { return nil, fmt.Errorf("trading is not enabled on this exchange: %s", exchangeType) @@ -258,10 +258,10 @@ func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, ccxt fmt.Printf("MakeTradingExchange received exchange headers: %s\n", headers) x, e := exchange.makeFn(exchangeFactoryData{ - simMode: simMode, - apiKeys: apiKeys, - ccxtParams: ccxtParams, - headers: headers, + simMode: simMode, + apiKeys: apiKeys, + exchangeParams: exchangeParams, + headers: headers, }) if e != nil { return nil, fmt.Errorf("error when making the '%s' exchange: %s", exchangeType, e) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index 20d7ef6e7..22e916e3e 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -28,20 +28,20 @@ func (t *exchangeAPIKeysToml) toExchangeAPIKeys() []api.ExchangeAPIKey { return apiKeys } -type ccxtParamsToml []struct { +type exchangeParamsToml []struct { Parameter string `valid:"-" toml:"PARAMETER"` Value string `valid:"-" toml:"VALUE"` } -func (c *ccxtParamsToml) toCcxtParams() []api.CcxtParam { - ccxtParams := []api.CcxtParam{} +func (c *exchangeParamsToml) toExchangeParams() []api.ExchangeParam { + exchangeParams := []api.ExchangeParam{} for _, param := range *c { - ccxtParams = append(ccxtParams, api.CcxtParam{ + exchangeParams = append(exchangeParams, api.ExchangeParam{ Parameter: param.Parameter, Value: param.Value, }) } - return ccxtParams + return exchangeParams } type exchangeHeadersToml []struct { @@ -71,7 +71,7 @@ type mirrorConfig struct { MinBaseVolume float64 `valid:"-" toml:"MIN_BASE_VOLUME"` OffsetTrades bool `valid:"-" toml:"OFFSET_TRADES"` ExchangeAPIKeys exchangeAPIKeysToml `valid:"-" toml:"EXCHANGE_API_KEYS"` - CcxtParams ccxtParamsToml `valid:"-" toml:"CCXT_PARAMS"` + ExchangeParams exchangeParamsToml `valid:"-" toml:"CCXT_PARAMS"` ExchangeHeaders exchangeHeadersToml `valid:"-" toml:"EXCHANGE_HEADERS"` } @@ -131,9 +131,9 @@ func makeMirrorStrategy(sdex *SDEX, ieif *IEIF, pair *model.TradingPair, baseAss var e error if config.OffsetTrades { exchangeAPIKeys := config.ExchangeAPIKeys.toExchangeAPIKeys() - ccxtParams := config.CcxtParams.toCcxtParams() + exchangeParams := config.ExchangeParams.toExchangeParams() exchangeHeaders := config.ExchangeHeaders.toExchangeHeaders() - exchange, e = MakeTradingExchange(config.Exchange, exchangeAPIKeys, ccxtParams, exchangeHeaders, simMode) + exchange, e = MakeTradingExchange(config.Exchange, exchangeAPIKeys, exchangeParams, exchangeHeaders, simMode) if e != nil { return nil, e } diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index a6a4d4848..8f0d4c038 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -53,7 +53,7 @@ type CcxtMarket struct { const pathExchanges = "/exchanges" // MakeInitializedCcxtExchange constructs an instance of Ccxt that is bound to a specific exchange instance on the CCXT REST server -func MakeInitializedCcxtExchange(exchangeName string, apiKey api.ExchangeAPIKey, params []api.CcxtParam, headers []api.ExchangeHeader) (*Ccxt, error) { +func MakeInitializedCcxtExchange(exchangeName string, apiKey api.ExchangeAPIKey, params []api.ExchangeParam, headers []api.ExchangeHeader) (*Ccxt, error) { if strings.HasSuffix(ccxtBaseURL, "/") { return nil, fmt.Errorf("invalid format for ccxtBaseURL: %s", ccxtBaseURL) } @@ -103,7 +103,7 @@ func loadExchangeList() { exchangeList = &output } -func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey, params []api.CcxtParam, headers []api.ExchangeHeader) error { +func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey, params []api.ExchangeParam, headers []api.ExchangeHeader) error { // validate that exchange name is in the exchange list exchangeListed := false el := GetExchangeList() @@ -189,7 +189,7 @@ func (c *Ccxt) hasInstance(instanceList []string) bool { return false } -func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey, params []api.CcxtParam) error { +func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey, params []api.ExchangeParam) error { data := map[string]string{} data["id"] = c.instanceName diff --git a/support/sdk/ccxt_test.go b/support/sdk/ccxt_test.go index 035fb93c9..4cbf7cb3c 100644 --- a/support/sdk/ccxt_test.go +++ b/support/sdk/ccxt_test.go @@ -42,7 +42,7 @@ func TestMakeValid(t *testing.T) { return } - _, e := MakeInitializedCcxtExchange("kraken", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) + _, e := MakeInitializedCcxtExchange("kraken", api.ExchangeAPIKey{}, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("unexpected error: %s", e)) return @@ -55,7 +55,7 @@ func TestMakeInvalid(t *testing.T) { return } - _, e := MakeInitializedCcxtExchange("missing-exchange", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) + _, e := MakeInitializedCcxtExchange("missing-exchange", api.ExchangeAPIKey{}, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e == nil { assert.Fail(t, "expected an error when trying to make and initialize an exchange that is missing: 'missing-exchange'") return @@ -73,7 +73,7 @@ func TestFetchTickers(t *testing.T) { return } - c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -94,7 +94,7 @@ func TestFetchTickersWithMissingSymbol(t *testing.T) { return } - c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange("binance", api.ExchangeAPIKey{}, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -183,7 +183,7 @@ func runTestFetchOrderBook(k orderbookTest, t *testing.T) { return } - c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -256,7 +256,7 @@ func TestFetchTrades(t *testing.T) { } { tradingPairString := strings.Replace(k.tradingPair, "/", "_", -1) t.Run(fmt.Sprintf("%s-%s", k.exchangeName, tradingPairString), func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, api.ExchangeAPIKey{}, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -311,7 +311,7 @@ func TestFetchMyTrades(t *testing.T) { } { tradingPairString := strings.Replace(k.tradingPair, "/", "_", -1) t.Run(fmt.Sprintf("%s-%s", k.exchangeName, tradingPairString), func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -388,7 +388,7 @@ func TestFetchBalance(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -436,7 +436,7 @@ func TestOpenOrders(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -519,7 +519,7 @@ func TestCreateLimitOrder(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return @@ -604,7 +604,7 @@ func TestCancelOrder(t *testing.T) { }, } { t.Run(k.exchangeName, func(t *testing.T) { - c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.CcxtParam{}, []api.ExchangeHeader{}) + c, e := MakeInitializedCcxtExchange(k.exchangeName, k.apiKey, []api.ExchangeParam{}, []api.ExchangeHeader{}) if e != nil { assert.Fail(t, fmt.Sprintf("error when making ccxt exchange: %s", e)) return From d75a0404c64d534a70db60e0a82a1bb579a9aaee Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Sun, 31 Mar 2019 23:51:38 -0500 Subject: [PATCH 05/26] update config.go --- trader/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trader/config.go b/trader/config.go index a425938b1..562a5ff89 100644 --- a/trader/config.go +++ b/trader/config.go @@ -47,10 +47,10 @@ type BotConfig struct { Key string `valid:"-" toml:"KEY"` Secret string `valid:"-" toml:"SECRET"` } `valid:"-" toml:"EXCHANGE_API_KEYS"` - CcxtParams []struct { + ExchangeParams []struct { Parameter string `valid:"-" toml:"PARAMETER"` Value string `valid:"-" toml:"VALUE"` - } `valid:"-" toml:"CCXT_PARAMS"` + } `valid:"-" toml:"EXCHANGE_PARAMS"` ExchangeHeaders []struct { Header string `valid:"-" toml:"HEADER"` Value string `valid:"-" toml:"VALUE"` @@ -68,7 +68,7 @@ type BotConfig struct { func (b BotConfig) String() string { return utils.StructString(b, map[string]func(interface{}) interface{}{ "EXCHANGE_API_KEYS": utils.Hide, - "CCXT_PARAMS": utils.Hide, + "EXCHANGE_PARAMS": utils.Hide, "EXCHANGE_HEADERS": utils.Hide, "SOURCE_SECRET_SEED": utils.SecretKey2PublicKey, "TRADING_SECRET_SEED": utils.SecretKey2PublicKey, From 4fb9ba80ade0d69fc81249cdae6460a511630690 Mon Sep 17 00:00:00 2001 From: Reid McCamish <43561569+Reidmcc@users.noreply.github.com> Date: Mon, 1 Apr 2019 11:46:21 -0500 Subject: [PATCH 06/26] remove added testOrderConstraints pair --- plugins/ccxtExchange_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/ccxtExchange_test.go b/plugins/ccxtExchange_test.go index 522366fa1..a3ba06e75 100644 --- a/plugins/ccxtExchange_test.go +++ b/plugins/ccxtExchange_test.go @@ -24,7 +24,6 @@ var supportedTradingExchanges = map[string]api.ExchangeAPIKey{ var testOrderConstraints = map[model.TradingPair]model.OrderConstraints{ *model.MakeTradingPair(model.XLM, model.USDT): *model.MakeOrderConstraints(4, 5, 0.1), *model.MakeTradingPair(model.XLM, model.BTC): *model.MakeOrderConstraints(8, 4, 1.0), - *model.MakeTradingPair(model.XLM, model.USD): *model.MakeOrderConstraints(6, 0, 1.0), } func TestGetTickerPrice_Ccxt(t *testing.T) { From 258f1d67d3899ed21c3ee69cefd6299ea1c8bd4a Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Mon, 1 Apr 2019 18:20:46 -0700 Subject: [PATCH 07/26] use better assetConverter for KrakenExchange#GetOpenOrders, closes #143 (#144) --- model/tradingPair.go | 29 +++++++++++++++++++++++++---- plugins/ieif.go | 6 ++++-- plugins/krakenExchange.go | 7 ++++--- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/model/tradingPair.go b/model/tradingPair.go index 8e7a6eb7a..1ad542ed3 100644 --- a/model/tradingPair.go +++ b/model/tradingPair.go @@ -48,14 +48,35 @@ func (p TradingPair) ToString(c *AssetConverter, delim string) (string, error) { // TradingPairFromString makes a TradingPair out of a string func TradingPairFromString(codeSize int8, c *AssetConverter, p string) (*TradingPair, error) { - base, e := c.FromString(p[0:codeSize]) + return TradingPairFromString2(codeSize, []*AssetConverter{c}, p) +} + +// TradingPairFromString2 makes a TradingPair out of a string +func TradingPairFromString2(codeSize int8, converters []*AssetConverter, p string) (*TradingPair, error) { + var base Asset + var quote Asset + var e error + + baseString := p[0:codeSize] + for _, c := range converters { + base, e = c.FromString(baseString) + if e == nil { + break + } + } if e != nil { - return nil, fmt.Errorf("base asset could not be converted: %s", e) + return nil, fmt.Errorf("base asset could not be converted using any of the converters in the list of %d converters: %s", len(converters), baseString) } - quote, e := c.FromString(p[codeSize : codeSize*2]) + quoteString := p[codeSize : codeSize*2] + for _, c := range converters { + quote, e = c.FromString(quoteString) + if e == nil { + break + } + } if e != nil { - return nil, fmt.Errorf("quote asset could not be converted: %s", e) + return nil, fmt.Errorf("quote asset could not be converted using any of the converters in the list of %d converters: %s", len(converters), quoteString) } return &TradingPair{Base: base, Quote: quote}, nil diff --git a/plugins/ieif.go b/plugins/ieif.go index 34f566c08..894302bfe 100644 --- a/plugins/ieif.go +++ b/plugins/ieif.go @@ -3,6 +3,7 @@ package plugins import ( "fmt" "log" + "strings" "github.com/stellar/go/clients/horizon" "github.com/stellar/kelp/api" @@ -168,15 +169,16 @@ func (ieif *IEIF) LogAllLiabilities(assetBase horizon.Asset, assetQuote horizon. } func (ieif *IEIF) logLiabilities(asset horizon.Asset, assetStr string) { + trimmedAssetStr := strings.TrimSpace(assetStr) l, e := ieif.assetLiabilities(asset) if e != nil { - log.Printf("could not fetch liability for asset '%s', error = %s\n", assetStr, e) + log.Printf("could not fetch liability for %s asset, error = %s\n", trimmedAssetStr, e) return } balance, e := ieif.assetBalance(asset) if e != nil { - log.Printf("cannot fetch balance for asset '%s', error = %s\n", assetStr, e) + log.Printf("cannot fetch balance for %s asset, error = %s\n", trimmedAssetStr, e) return } // TODO don't break out into vars diff --git a/plugins/krakenExchange.go b/plugins/krakenExchange.go index 4aadae595..f8149b50a 100644 --- a/plugins/krakenExchange.go +++ b/plugins/krakenExchange.go @@ -220,12 +220,13 @@ func (k *krakenExchange) GetOpenOrders(pairs []*model.TradingPair) (map[model.Tr return nil, e } + assetConverters := []*model.AssetConverter{k.assetConverterOpenOrders, model.Display} m := map[model.TradingPair][]model.OpenOrder{} for ID, o := range openOrdersResponse.Open { // kraken uses different symbols when fetching open orders! - pair, e := model.TradingPairFromString(3, k.assetConverterOpenOrders, o.Description.AssetPair) + pair, e := model.TradingPairFromString2(3, assetConverters, o.Description.AssetPair) if e != nil { - return nil, e + return nil, fmt.Errorf("error parsing trading pair '%s' in krakenExchange#GetOpenOrders: %s", o.Description.AssetPair, e) } if _, ok := pairsMap[*pair]; !ok { @@ -376,7 +377,7 @@ func (k *krakenExchange) getTradeHistory(tradingPair model.TradingPair, maybeCur var pair *model.TradingPair pair, e = model.TradingPairFromString(4, k.assetConverter, _pair) if e != nil { - return nil, e + return nil, fmt.Errorf("error parsing trading pair '%s' in krakenExchange#getTradeHistory: %s", _pair, e) } orderConstraints := k.GetOrderConstraints(pair) // for now use the max precision between price and volume for fee and cost From df70efabae01be08bf2fc0210e28048b9050b631 Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Mon, 1 Apr 2019 22:44:50 -0500 Subject: [PATCH 08/26] ccxt testing progress --- plugins/ccxtExchange_test.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/plugins/ccxtExchange_test.go b/plugins/ccxtExchange_test.go index 522366fa1..07cedaefb 100644 --- a/plugins/ccxtExchange_test.go +++ b/plugins/ccxtExchange_test.go @@ -15,10 +15,9 @@ import ( var supportedExchanges = []string{"binance"} var emptyAPIKey = api.ExchangeAPIKey{} var emptyParams = api.ExchangeParam{} -var emptyHeader = api.ExchangeHeader{} var supportedTradingExchanges = map[string]api.ExchangeAPIKey{ - "binance": {}, - "coinbasepro": {}, + "binance": {}, + // "coinbasepro": {}, } var testOrderConstraints = map[model.TradingPair]model.OrderConstraints{ @@ -34,7 +33,7 @@ func TestGetTickerPrice_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -61,7 +60,7 @@ func TestGetOrderBook_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -94,7 +93,7 @@ func TestGetTrades_Ccxt(t *testing.T) { for _, exchangeName := range supportedExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -119,7 +118,7 @@ func TestGetTradeHistory_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -177,7 +176,7 @@ func validateTrades(t *testing.T, pair model.TradingPair, trades []model.Trade) func TestGetLatestTradeCursor_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -216,7 +215,7 @@ func TestGetAccountBalances_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -261,7 +260,7 @@ func TestGetOpenOrders_Ccxt(t *testing.T) { for exchangeName, apiKey := range supportedTradingExchanges { for _, pair := range tradingPairs { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -376,7 +375,7 @@ func TestAddOrder_Ccxt(t *testing.T) { }, } { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -426,7 +425,7 @@ func TestCancelOrder_Ccxt(t *testing.T) { }, } { t.Run(exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(exchangeName, testOrderConstraints, []api.ExchangeAPIKey{apiKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } @@ -483,7 +482,7 @@ func TestGetOrderConstraints_Ccxt_Precision(t *testing.T) { for _, kase := range testCases { t.Run(kase.exchangeName, func(t *testing.T) { - testCcxtExchange, e := makeCcxtExchange(kase.exchangeName, nil, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{emptyHeader}, false) + testCcxtExchange, e := makeCcxtExchange(kase.exchangeName, nil, []api.ExchangeAPIKey{emptyAPIKey}, []api.ExchangeParam{emptyParams}, []api.ExchangeHeader{}, false) if !assert.NoError(t, e) { return } From 57fcda5c9215960aa8a061f81236625dd6b245ad Mon Sep 17 00:00:00 2001 From: Reidmcc Date: Mon, 1 Apr 2019 23:11:27 -0500 Subject: [PATCH 09/26] remove coinbase from precision checks --- plugins/ccxtExchange_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/ccxtExchange_test.go b/plugins/ccxtExchange_test.go index 6130e5852..f93fc92ac 100644 --- a/plugins/ccxtExchange_test.go +++ b/plugins/ccxtExchange_test.go @@ -471,12 +471,13 @@ func TestGetOrderConstraints_Ccxt_Precision(t *testing.T) { wantPricePrecision: 8, wantVolPrecision: 0, }, - { - exchangeName: "coinbasepro", - pair: &model.TradingPair{Base: model.XLM, Quote: model.USD}, - wantPricePrecision: 6, - wantVolPrecision: 0, - }, + // disabled until volume precision is fixed + // { + // exchangeName: "coinbasepro", + // pair: &model.TradingPair{Base: model.XLM, Quote: model.USD}, + // wantPricePrecision: 6, + // wantVolPrecision: 0, + // }, } for _, kase := range testCases { From 6e879e2e689861370f41302b363039fcd0d3aae4 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 3 Apr 2019 10:12:17 -0700 Subject: [PATCH 10/26] update go version used in travis CI --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c49c37ca7..52125e3e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,8 @@ services: go: - "1.10.x" -- "1.11" +- "1.11.x" +- "1.12.x" before_install: - curl https://glide.sh/get | sh From 82e58b49381973efa5adc12c0f0238432f6cce2c Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Thu, 4 Apr 2019 16:18:06 -0700 Subject: [PATCH 11/26] turn off minBaseVolume checks in mirror strategy when offsetTrades=false, closes #146 --- plugins/mirrorStrategy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index 4454b2eb6..bb5224159 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -374,7 +374,7 @@ func (s *mirrorStrategy) doModifyOffer( // convert the precision from the backing exchange to the primary exchange offerPrice := model.NumberByCappingPrecision(price, s.primaryConstraints.PricePrecision) offerAmount := model.NumberByCappingPrecision(vol, s.primaryConstraints.VolumePrecision) - if offerAmount.AsFloat() < s.backingConstraints.MinBaseVolume.AsFloat() { + if s.offsetTrades && offerAmount.AsFloat() < s.backingConstraints.MinBaseVolume.AsFloat() { log.Printf("deleting level, baseVolume (%f) on backing exchange dropped below minBaseVolume of backing exchange (%f)\n", offerAmount.AsFloat(), s.backingConstraints.MinBaseVolume.AsFloat()) deleteOp := s.sdex.DeleteOffer(oldOffer) From d21a75fcdbff323de46e3d2a46f37d64831b1cb7 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Thu, 4 Apr 2019 18:04:26 -0700 Subject: [PATCH 12/26] Use app name and version headers from horizon client in Go SDKs (#147) --- .gitignore | 1 + cmd/terminate.go | 6 ++++-- cmd/trade.go | 37 ++++++++++++++++++++++++++++++++++--- glide.lock | 7 ++++--- glide.yaml | 3 ++- plugins/sdexExtensions.go | 11 ++--------- support/prefs/prefs.go | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 83 insertions(+), 18 deletions(-) create mode 100644 support/prefs/prefs.go diff --git a/.gitignore b/.gitignore index 94852f802..5fe6322a1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ vendor/ build/ bin/ .idea +kelp.prefs diff --git a/cmd/terminate.go b/cmd/terminate.go index a775ee9c4..ca1397b4a 100644 --- a/cmd/terminate.go +++ b/cmd/terminate.go @@ -38,8 +38,10 @@ func init() { // --- start initialization of objects ---- client := &horizon.Client{ - URL: configFile.HorizonURL, - HTTP: http.DefaultClient, + URL: configFile.HorizonURL, + HTTP: http.DefaultClient, + AppName: "kelp", + AppVersion: version, } sdex := plugins.MakeSDEX( client, diff --git a/cmd/trade.go b/cmd/trade.go index 77b725884..40d28c596 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/stellar/go/build" "github.com/stellar/go/clients/horizon" + horizonclient "github.com/stellar/go/exp/clients/horizon" "github.com/stellar/go/support/config" "github.com/stellar/kelp/api" "github.com/stellar/kelp/model" @@ -21,6 +22,7 @@ import ( "github.com/stellar/kelp/support/logger" "github.com/stellar/kelp/support/monitoring" "github.com/stellar/kelp/support/networking" + "github.com/stellar/kelp/support/prefs" "github.com/stellar/kelp/support/utils" "github.com/stellar/kelp/trader" ) @@ -28,6 +30,8 @@ import ( const tradeExamples = ` kelp trade --botConf ./path/trader.cfg --strategy buysell --stratConf ./path/buysell.cfg kelp trade --botConf ./path/trader.cfg --strategy buysell --stratConf ./path/buysell.cfg --sim` +const prefsFilename = "kelp.prefs" + var tradeCmd = &cobra.Command{ Use: "trade", Short: "Trades against the Stellar universal marketplace using the specified strategy", @@ -64,6 +68,7 @@ type inputs struct { simMode *bool logPrefix *string fixedIterations *uint64 + noHeaders *bool } func validateCliParams(l logger.Logger, options inputs) { @@ -105,6 +110,7 @@ func init() { options.simMode = tradeCmd.Flags().Bool("sim", false, "simulate the bot's actions without placing any trades") options.logPrefix = tradeCmd.Flags().StringP("log", "l", "", "log to a file (and stdout) with this prefix for the filename") options.fixedIterations = tradeCmd.Flags().Uint64("iter", 0, "only run the bot for the first N iterations (defaults value 0 runs unboundedly)") + options.noHeaders = tradeCmd.Flags().Bool("no-headers", false, "do not set X-App-Name and X-App-Version headers on requests to horizon") requiredFlag("botConf") requiredFlag("strategy") @@ -125,16 +131,16 @@ func makeStartupMessage(options inputs) string { return startupMessage } -func makeFeeFn(l logger.Logger, botConfig trader.BotConfig) plugins.OpFeeStroops { +func makeFeeFn(l logger.Logger, botConfig trader.BotConfig, newClient *horizonclient.Client) plugins.OpFeeStroops { if !botConfig.IsTradingSdex() { return plugins.SdexFixedFeeFn(0) } feeFn, e := plugins.SdexFeeFnFromStats( - botConfig.HorizonURL, botConfig.Fee.CapacityTrigger, botConfig.Fee.Percentile, botConfig.Fee.MaxOpFeeStroops, + newClient, ) if e != nil { logger.Fatal(l, fmt.Errorf("could not set up feeFn correctly: %s", e)) @@ -171,6 +177,7 @@ func makeExchangeShimSdex( botConfig trader.BotConfig, options inputs, client *horizon.Client, + newClient *horizonclient.Client, ieif *plugins.IEIF, network build.Network, threadTracker *multithreading.ThreadTracker, @@ -201,7 +208,7 @@ func makeExchangeShimSdex( tradingPair.Base: botConfig.AssetBase(), tradingPair.Quote: botConfig.AssetQuote(), } - feeFn := makeFeeFn(l, botConfig) + feeFn := makeFeeFn(l, botConfig, newClient) sdex := plugins.MakeSDEX( client, ieif, @@ -331,6 +338,29 @@ func runTradeCmd(options inputs) { URL: botConfig.HorizonURL, HTTP: http.DefaultClient, } + newClient := &horizonclient.Client{ + // TODO horizonclient.Client has a bug in it where it does not use "/" to separate the horizonURL from the fee_stats endpoint + HorizonURL: botConfig.HorizonURL + "/", + HTTP: http.DefaultClient, + } + if !*options.noHeaders { + client.AppName = "kelp" + client.AppVersion = version + newClient.AppName = "kelp" + newClient.AppVersion = version + + p := prefs.Make(prefsFilename) + if p.FirstTime() { + log.Printf("Kelp sets the 'X-App-Name' and 'X-App-Version' headers on requests made to Horizon. These can be turned off using the --no-headers flag. See `kelp trade --help` for more information.\n") + e := p.SetNotFirstTime() + if e != nil { + l.Info("") + l.Errorf("unable to create preferences file: %s", e) + // we can still proceed with this error + } + } + } + ieif := plugins.MakeIEIF(botConfig.IsTradingSdex()) network := utils.ParseNetwork(botConfig.HorizonURL) exchangeShim, sdex := makeExchangeShimSdex( @@ -338,6 +368,7 @@ func runTradeCmd(options inputs) { botConfig, options, client, + newClient, ieif, network, threadTracker, diff --git a/glide.lock b/glide.lock index b5bd6a135..e17f93f90 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 0ab15bf176f8313235c90ce78694e51ca8da996fd0c80187810a19536864ca1b -updated: 2019-03-26T14:36:41.239077092-07:00 +hash: c1a6fe42c2e0796f29822018134adc2401292b654fec58b7d790daafceec2585 +updated: 2019-04-03T12:27:32.117314051-07:00 imports: - name: cloud.google.com/go version: 793297ec250352b0ece46e103381a0fc3dab95a1 @@ -74,7 +74,7 @@ imports: - name: github.com/spf13/viper version: db7ff930a189b98d602179d9001d33345f42b8c7 - name: github.com/stellar/go - version: 61ed9984b1f88fda0d4e5e375021b404d9900959 + version: a45d1ae036357adf337060c7b15dbe9abc235025 subpackages: - amount - build @@ -88,6 +88,7 @@ imports: - protocols/horizon - protocols/horizon/base - protocols/horizon/effects + - protocols/horizon/operations - strkey - support/app - support/config diff --git a/glide.yaml b/glide.yaml index f94b7b1fd..600c64020 100644 --- a/glide.yaml +++ b/glide.yaml @@ -9,10 +9,11 @@ import: - package: github.com/spf13/pflag version: v1.0.0 - package: github.com/stellar/go - version: 61ed9984b1f88fda0d4e5e375021b404d9900959 + version: a45d1ae036357adf337060c7b15dbe9abc235025 subpackages: - build - clients/horizon + - exp/clients/horizon - support/config - support/errors ############################################################## diff --git a/plugins/sdexExtensions.go b/plugins/sdexExtensions.go index 35b6b2634..73f847c66 100644 --- a/plugins/sdexExtensions.go +++ b/plugins/sdexExtensions.go @@ -3,7 +3,6 @@ package plugins import ( "fmt" "log" - "net/http" "github.com/stellar/go/exp/clients/horizon" hProtocol "github.com/stellar/go/protocols/horizon" @@ -25,10 +24,10 @@ var validPercentiles = []uint8{10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99} // SdexFeeFnFromStats returns an OpFeeStroops that uses the /fee_stats endpoint func SdexFeeFnFromStats( - horizonBaseURL string, capacityTrigger float64, percentile uint8, maxOpFeeStroops uint64, + newClient *horizonclient.Client, ) (OpFeeStroops, error) { isValid := false for _, p := range validPercentiles { @@ -49,14 +48,8 @@ func SdexFeeFnFromStats( return nil, fmt.Errorf("unable to create SdexFeeFnFromStats, maxOpFeeStroops should be >= %d (baseFeeStroops): %d", baseFeeStroops, maxOpFeeStroops) } - client := &horizonclient.Client{ - // TODO horizonclient.Client has a bug in it where it does not use "/" to separate the horizonURL from the fee_stats endpoint - HorizonURL: horizonBaseURL + "/", - HTTP: http.DefaultClient, - } - return func() (uint64, error) { - return getFeeFromStats(client, capacityTrigger, percentile, maxOpFeeStroops) + return getFeeFromStats(newClient, capacityTrigger, percentile, maxOpFeeStroops) }, nil } diff --git a/support/prefs/prefs.go b/support/prefs/prefs.go new file mode 100644 index 000000000..8af0bee04 --- /dev/null +++ b/support/prefs/prefs.go @@ -0,0 +1,36 @@ +package prefs + +import ( + "fmt" + "os" +) + +// Preferences denotes a preferences file +type Preferences struct { + filepath string +} + +// Make creates a new Preferences struct +func Make(filepath string) *Preferences { + return &Preferences{ + filepath: filepath, + } +} + +// FirstTime checks for the existence of the file +func (p *Preferences) FirstTime() bool { + if _, e := os.Stat(p.filepath); os.IsNotExist(e) { + return true + } + return false +} + +// SetNotFirstTime saves a file on the file system to denote that this is not the first time +func (p *Preferences) SetNotFirstTime() error { + emptyFile, e := os.Create(p.filepath) + if e != nil { + return fmt.Errorf("could not create file '%s' when setting not first time prefs file: %s", p.filepath, e) + } + emptyFile.Close() + return nil +} From 96c9e88fe3b976077ed1010a26a5c90a74c9e26e Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Thu, 4 Apr 2019 18:07:02 -0700 Subject: [PATCH 13/26] update Running Kelp section of README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f38ef5964..f6bb8f71e 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,8 @@ The `trade` command has three required parameters which are: - **strategy**: the strategy you want to run (_sell_, _buysell_, _balanced_, _mirror_, _delete_). - **stratConf**: full path to the _.cfg_ file specific to your chosen strategy, [sample files here](examples/configs/trader/). +Kelp sets the `X-App-Name` and `X-App-Version` headers on requests made to Horizon. These can be turned off using the `--no-headers` flag. See `kelp trade --help` for more information. + Here's an example of how to start the trading bot with the _buysell_ strategy: `kelp trade --botConf ./path/trader.cfg --strategy buysell --stratConf ./path/buysell.cfg` From 50b50531725668fee3321ca514cba85bc8ba316c Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Fri, 5 Apr 2019 16:01:58 -0700 Subject: [PATCH 14/26] update Kelp headers message --- README.md | 2 +- cmd/trade.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6bb8f71e..978ce8a11 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ The `trade` command has three required parameters which are: - **strategy**: the strategy you want to run (_sell_, _buysell_, _balanced_, _mirror_, _delete_). - **stratConf**: full path to the _.cfg_ file specific to your chosen strategy, [sample files here](examples/configs/trader/). -Kelp sets the `X-App-Name` and `X-App-Version` headers on requests made to Horizon. These can be turned off using the `--no-headers` flag. See `kelp trade --help` for more information. +Kelp sets the `X-App-Name` and `X-App-Version` headers on requests made to Horizon. These headers help us track overall Kelp usage, so that we can learn about general usage patterns and adapt Kelp to be more useful in the future. These can be turned off using the `--no-headers` flag. See `kelp trade --help` for more information. Here's an example of how to start the trading bot with the _buysell_ strategy: diff --git a/cmd/trade.go b/cmd/trade.go index 40d28c596..609b6b422 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -351,7 +351,7 @@ func runTradeCmd(options inputs) { p := prefs.Make(prefsFilename) if p.FirstTime() { - log.Printf("Kelp sets the 'X-App-Name' and 'X-App-Version' headers on requests made to Horizon. These can be turned off using the --no-headers flag. See `kelp trade --help` for more information.\n") + log.Printf("Kelp sets the `X-App-Name` and `X-App-Version` headers on requests made to Horizon. These headers help us track overall Kelp usage, so that we can learn about general usage patterns and adapt Kelp to be more useful in the future. These can be turned off using the `--no-headers` flag. See `kelp trade --help` for more information.\n") e := p.SetNotFirstTime() if e != nil { l.Info("") From 6d989c8d78ea7c088e36b2b6f2bb7679013617d0 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Tue, 9 Apr 2019 20:10:10 -0700 Subject: [PATCH 15/26] Add Overrides for remaining orderConstraints via config, closes #141, closes #152 (#153) renamed `MIN_CENTRALIZED_BASE_VOLUME` to `CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE` in trader config, see sample config files for latest examples --- api/exchange.go | 2 + cmd/trade.go | 47 ++++++++-- examples/configs/trader/sample_mirror.cfg | 10 ++- examples/configs/trader/sample_trader.cfg | 11 ++- model/orderbook.go | 95 +++++++++++++++++++++ plugins/batchedExchange.go | 11 ++- plugins/ccxtExchange.go | 39 +++++---- plugins/krakenExchange.go | 30 +++++-- plugins/krakenExchange_test.go | 11 +-- plugins/mirrorStrategy.go | 69 +++++++++++---- plugins/orderConstraintsOverridesHandler.go | 62 ++++++++++++++ plugins/sdex.go | 25 ++++-- support/utils/configs.go | 18 ++++ trader/config.go | 71 ++++++++------- trader/trader.go | 17 ++-- 15 files changed, 402 insertions(+), 116 deletions(-) create mode 100644 plugins/orderConstraintsOverridesHandler.go diff --git a/api/exchange.go b/api/exchange.go index ee1845921..23c0fb648 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -73,6 +73,8 @@ type FillTrackable interface { type Constrainable interface { // return nil if the constraint does not exist for the exchange GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints + + OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) } // OrderbookFetcher extracts out the method that should go into ExchangeShim for now diff --git a/cmd/trade.go b/cmd/trade.go index 609b6b422..7a7c579ca 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -93,8 +93,19 @@ func validateBotConfig(l logger.Logger, botConfig trader.BotConfig) { logger.Fatal(l, fmt.Errorf("The `FEE` object needs to exist in the trader config file when trading on SDEX")) } - if !botConfig.IsTradingSdex() && botConfig.MinCentralizedBaseVolume == 0.0 { - logger.Fatal(l, fmt.Errorf("need to specify non-zero MIN_CENTRALIZED_BASE_VOLUME config param in trader config file when not trading on SDEX")) + if !botConfig.IsTradingSdex() && botConfig.CentralizedMinBaseVolumeOverride != nil && *botConfig.CentralizedMinBaseVolumeOverride <= 0.0 { + logger.Fatal(l, fmt.Errorf("need to specify positive CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE config param in trader config file when not trading on SDEX")) + } + if !botConfig.IsTradingSdex() && botConfig.CentralizedMinQuoteVolumeOverride != nil && *botConfig.CentralizedMinQuoteVolumeOverride <= 0.0 { + logger.Fatal(l, fmt.Errorf("need to specify positive CENTRALIZED_MIN_QUOTE_VOLUME_OVERRIDE config param in trader config file when not trading on SDEX")) + } + validatePrecisionConfig(l, botConfig.IsTradingSdex(), botConfig.CentralizedVolumePrecisionOverride, "CENTRALIZED_VOLUME_PRECISION_OVERRIDE") + validatePrecisionConfig(l, botConfig.IsTradingSdex(), botConfig.CentralizedPricePrecisionOverride, "CENTRALIZED_PRICE_PRECISION_OVERRIDE") +} + +func validatePrecisionConfig(l logger.Logger, isTradingSdex bool, precisionField *int8, name string) { + if !isTradingSdex && precisionField != nil && *precisionField < 0 { + logger.Fatal(l, fmt.Errorf("need to specify non-negative %s config param in trader config file when not trading on SDEX", name)) } } @@ -202,6 +213,33 @@ func makeExchangeShimSdex( } exchangeShim = plugins.MakeBatchedExchange(exchangeAPI, *options.simMode, botConfig.AssetBase(), botConfig.AssetQuote(), botConfig.TradingAccount()) + + // update precision overrides + exchangeShim.OverrideOrderConstraints(tradingPair, model.MakeOrderConstraintsOverride( + botConfig.CentralizedPricePrecisionOverride, + botConfig.CentralizedVolumePrecisionOverride, + nil, + nil, + )) + if botConfig.CentralizedMinBaseVolumeOverride != nil { + // use updated precision overrides to convert the minCentralizedBaseVolume to a model.Number + exchangeShim.OverrideOrderConstraints(tradingPair, model.MakeOrderConstraintsOverride( + nil, + nil, + model.NumberFromFloat(*botConfig.CentralizedMinBaseVolumeOverride, exchangeShim.GetOrderConstraints(tradingPair).VolumePrecision), + nil, + )) + } + if botConfig.CentralizedMinQuoteVolumeOverride != nil { + // use updated precision overrides to convert the minCentralizedQuoteVolume to a model.Number + minQuoteVolume := model.NumberFromFloat(*botConfig.CentralizedMinQuoteVolumeOverride, exchangeShim.GetOrderConstraints(tradingPair).VolumePrecision) + exchangeShim.OverrideOrderConstraints(tradingPair, model.MakeOrderConstraintsOverride( + nil, + nil, + nil, + &minQuoteVolume, + )) + } } sdexAssetMap := map[model.Asset]horizon.Asset{ @@ -294,17 +332,12 @@ func makeBot( if e != nil { l.Infof("Unable to set up monitoring for alert type '%s' with the given API key\n", botConfig.AlertType) } - minCentralizedBaseVolume := &botConfig.MinCentralizedBaseVolume - if botConfig.IsTradingSdex() { - minCentralizedBaseVolume = nil - } bot := trader.MakeBot( client, ieif, botConfig.AssetBase(), botConfig.AssetQuote(), tradingPair, - minCentralizedBaseVolume, botConfig.TradingAccount(), sdex, exchangeShim, diff --git a/examples/configs/trader/sample_mirror.cfg b/examples/configs/trader/sample_mirror.cfg index 3090a2730..0c305f97f 100644 --- a/examples/configs/trader/sample_mirror.cfg +++ b/examples/configs/trader/sample_mirror.cfg @@ -36,10 +36,16 @@ VOLUME_DIVIDE_BY=500.0 # in this example the spread is 0.5% PER_LEVEL_SPREAD=0.005 -# minimum volume of base units needed to place an order on the backing exchange # minimum values for Kraken: https://support.kraken.com/hc/en-us/articles/205893708-What-is-the-minimum-order-size-volume- # minimum order value for Binance: https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule -MIN_BASE_VOLUME=30.0 +# (optional) number of decimal units to be used for price, which is specified in units of the quote asset, needed to place an order on the backing exchange +#PRICE_PRECISION_OVERRIDE=6 +# (optional) number of decimal units to be used for volume, which is specified in units of the base asset, needed to place an order on the backing exchange +#VOLUME_PRECISION_OVERRIDE=1 +# (optional) minimum volume of base units needed to place an order on the backing exchange +#MIN_BASE_VOLUME_OVERRIDE=30.0 +# (optional) minimum volume of quote units needed to place an order on the backing exchange +#MIN_QUOTE_VOLUME_OVERRIDE=30.0 # set to true if you want the bot to offset your trades onto the backing exchange to realize the per_level_spread against each trade # requires you to specify the EXCHANGE_API_KEYS below diff --git a/examples/configs/trader/sample_trader.cfg b/examples/configs/trader/sample_trader.cfg index 947b2d7c2..f4a66447c 100644 --- a/examples/configs/trader/sample_trader.cfg +++ b/examples/configs/trader/sample_trader.cfg @@ -85,10 +85,17 @@ MAX_OP_FEE_STROOPS=5000 # Google authentication. #ACCEPTABLE_GOOGLE_EMAILS="" -# minimum volume of base units needed to place an order on the non-sdex (centralized) exchange # minimum values for Kraken: https://support.kraken.com/hc/en-us/articles/205893708-What-is-the-minimum-order-size-volume- # minimum order value for Binance: https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule -#MIN_CENTRALIZED_BASE_VOLUME=30.0 +# (optional) number of decimal units to be used for price, which is specified in units of the quote asset, to place an order on the non-sdex (centralized) exchange +#CENTRALIZED_PRICE_PRECISION_OVERRIDE=6 +# (optional) number of decimal units to be used for volume, which is specified in units of the base asset, to place an order on the non-sdex (centralized) exchange +#CENTRALIZED_VOLUME_PRECISION_OVERRIDE=1 +# (optional) minimum volume of base units needed to place an order on the non-sdex (centralized) exchange +#CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE=30.0 +# (optional) minimum volume of quote units needed to place an order on the non-sdex (centralized) exchange +#CENTRALIZED_MIN_QUOTE_VOLUME_OVERRIDE=10.0 + # uncomment lines below to use kraken. Can use "sdex" or leave out to trade on the Stellar Decentralized Exchange. # can alternatively use any of the ccxt-exchanges marked as "Trading" (run `kelp exchanges` for full list) #TRADING_EXCHANGE="kraken" diff --git a/model/orderbook.go b/model/orderbook.go index f16fae3a6..44940d6fe 100644 --- a/model/orderbook.go +++ b/model/orderbook.go @@ -305,6 +305,28 @@ func MakeOrderConstraintsWithCost(pricePrecision int8, volumePrecision int8, min } } +// MakeOrderConstraintsWithOverride is a factory method for OrderConstraints, oc is not a pointer because we want a copy since we modify it +func MakeOrderConstraintsWithOverride(oc OrderConstraints, override *OrderConstraintsOverride) *OrderConstraints { + if override.PricePrecision != nil { + oc.PricePrecision = *override.PricePrecision + } + if override.VolumePrecision != nil { + oc.VolumePrecision = *override.VolumePrecision + } + if override.MinBaseVolume != nil { + oc.MinBaseVolume = *override.MinBaseVolume + } + if override.MinQuoteVolume != nil { + oc.MinQuoteVolume = *override.MinQuoteVolume + } + return &oc +} + +// MakeOrderConstraintsFromOverride is a factory method to convert an OrderConstraintsOverride to an OrderConstraints +func MakeOrderConstraintsFromOverride(override *OrderConstraintsOverride) *OrderConstraints { + return MakeOrderConstraintsWithOverride(OrderConstraints{}, override) +} + // OrderConstraints describes constraints when placing orders on an excahnge func (o *OrderConstraints) String() string { minQuoteVolumeStr := nilString @@ -315,3 +337,76 @@ func (o *OrderConstraints) String() string { return fmt.Sprintf("OrderConstraints[PricePrecision: %d, VolumePrecision: %d, MinBaseVolume: %s, MinQuoteVolume: %s]", o.PricePrecision, o.VolumePrecision, o.MinBaseVolume.AsString(), minQuoteVolumeStr) } + +// OrderConstraintsOverride describes an override for an OrderConstraint +type OrderConstraintsOverride struct { + PricePrecision *int8 + VolumePrecision *int8 + MinBaseVolume *Number + MinQuoteVolume **Number +} + +// MakeOrderConstraintsOverride is a factory method +func MakeOrderConstraintsOverride( + pricePrecision *int8, + volumePrecision *int8, + minBaseVolume *Number, + minQuoteVolume **Number, +) *OrderConstraintsOverride { + return &OrderConstraintsOverride{ + PricePrecision: pricePrecision, + VolumePrecision: volumePrecision, + MinBaseVolume: minBaseVolume, + MinQuoteVolume: minQuoteVolume, + } +} + +// MakeOrderConstraintsOverrideFromConstraints is a factory method for OrderConstraintsOverride +func MakeOrderConstraintsOverrideFromConstraints(oc *OrderConstraints) *OrderConstraintsOverride { + return &OrderConstraintsOverride{ + PricePrecision: &oc.PricePrecision, + VolumePrecision: &oc.VolumePrecision, + MinBaseVolume: &oc.MinBaseVolume, + MinQuoteVolume: &oc.MinQuoteVolume, + } +} + +// IsComplete returns true if the override contains all values +func (override *OrderConstraintsOverride) IsComplete() bool { + if override.PricePrecision == nil { + return false + } + + if override.VolumePrecision == nil { + return false + } + + if override.MinBaseVolume == nil { + return false + } + + if override.MinQuoteVolume == nil { + return false + } + + return true +} + +// Augment only updates values if updates are non-nil +func (override *OrderConstraintsOverride) Augment(updates *OrderConstraintsOverride) { + if updates.PricePrecision != nil { + override.PricePrecision = updates.PricePrecision + } + + if updates.VolumePrecision != nil { + override.VolumePrecision = updates.VolumePrecision + } + + if updates.MinBaseVolume != nil { + override.MinBaseVolume = updates.MinBaseVolume + } + + if updates.MinQuoteVolume != nil { + override.MinQuoteVolume = updates.MinQuoteVolume + } +} diff --git a/plugins/batchedExchange.go b/plugins/batchedExchange.go index ca63c47d6..575524c13 100644 --- a/plugins/batchedExchange.go +++ b/plugins/batchedExchange.go @@ -154,6 +154,11 @@ func (b BatchedExchange) GetOrderConstraints(pair *model.TradingPair) *model.Ord return b.inner.GetOrderConstraints(pair) } +// OverrideOrderConstraints impl, can partially override values for specific pairs +func (b BatchedExchange) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) { + b.inner.OverrideOrderConstraints(pair, override) +} + // GetOrderBook impl func (b BatchedExchange) GetOrderBook(pair *model.TradingPair, maxCount int32) (*model.OrderBook, error) { return b.inner.GetOrderBook(pair, maxCount) @@ -193,12 +198,6 @@ func (b BatchedExchange) SubmitOps(ops []build.TransactionMutator, asyncCallback return nil } - pair := &model.TradingPair{ - Base: model.FromHorizonAsset(b.baseAsset), - Quote: model.FromHorizonAsset(b.quoteAsset), - } - log.Printf("order constraints for trading pair %s: %s", pair, b.inner.GetOrderConstraints(pair)) - results := []submitResult{} numProcessed := 0 for _, c := range b.commands { diff --git a/plugins/ccxtExchange.go b/plugins/ccxtExchange.go index 3ea94250c..ee4dda53f 100644 --- a/plugins/ccxtExchange.go +++ b/plugins/ccxtExchange.go @@ -20,11 +20,11 @@ var _ api.Exchange = ccxtExchange{} // ccxtExchange is the implementation for the CCXT REST library that supports many exchanges (https://github.com/franz-see/ccxt-rest, https://github.com/ccxt/ccxt/) type ccxtExchange struct { - assetConverter *model.AssetConverter - delimiter string - orderConstraints map[model.TradingPair]model.OrderConstraints - api *sdk.Ccxt - simMode bool + assetConverter *model.AssetConverter + delimiter string + ocOverridesHandler *OrderConstraintsOverridesHandler + api *sdk.Ccxt + simMode bool } // makeCcxtExchange is a factory method to make an exchange using the CCXT interface @@ -47,16 +47,17 @@ func makeCcxtExchange( return nil, fmt.Errorf("error making a ccxt exchange: %s", e) } - if orderConstraintOverrides == nil { - orderConstraintOverrides = map[model.TradingPair]model.OrderConstraints{} + ocOverridesHandler := MakeEmptyOrderConstraintsOverridesHandler() + if orderConstraintOverrides != nil { + ocOverridesHandler = MakeOrderConstraintsOverridesHandler(orderConstraintOverrides) } return ccxtExchange{ - assetConverter: model.CcxtAssetConverter, - delimiter: "/", - orderConstraints: orderConstraintOverrides, - api: c, - simMode: simMode, + assetConverter: model.CcxtAssetConverter, + delimiter: "/", + ocOverridesHandler: ocOverridesHandler, + api: c, + simMode: simMode, }, nil } @@ -99,10 +100,6 @@ func (c ccxtExchange) GetAssetConverter() *model.AssetConverter { // GetOrderConstraints impl func (c ccxtExchange) GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints { - if oc, ok := c.orderConstraints[*pair]; ok { - return &oc - } - pairString, e := pair.ToString(c.assetConverter, c.delimiter) if e != nil { // this should never really panic because we would have converted this trading pair to a string previously @@ -114,12 +111,14 @@ func (c ccxtExchange) GetOrderConstraints(pair *model.TradingPair) *model.OrderC if ccxtMarket == nil { panic(fmt.Errorf("CCXT does not have precision and limit data for the passed in market: %s", pairString)) } - oc := *model.MakeOrderConstraintsWithCost(ccxtMarket.Precision.Price, ccxtMarket.Precision.Amount, ccxtMarket.Limits.Amount.Min, ccxtMarket.Limits.Cost.Min) + oc := model.MakeOrderConstraintsWithCost(ccxtMarket.Precision.Price, ccxtMarket.Precision.Amount, ccxtMarket.Limits.Amount.Min, ccxtMarket.Limits.Cost.Min) - // cache it before returning - c.orderConstraints[*pair] = oc + return c.ocOverridesHandler.Apply(pair, oc) +} - return &oc +// OverrideOrderConstraints impl, can partially override values for specific pairs +func (c ccxtExchange) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) { + c.ocOverridesHandler.Upsert(pair, override) } // GetAccountBalances impl diff --git a/plugins/krakenExchange.go b/plugins/krakenExchange.go index f8149b50a..94c575a16 100644 --- a/plugins/krakenExchange.go +++ b/plugins/krakenExchange.go @@ -29,6 +29,7 @@ type krakenExchange struct { apis []*krakenapi.KrakenApi apiNextIndex uint8 delimiter string + ocOverridesHandler *OrderConstraintsOverridesHandler withdrawKeys asset2Address2Key isSimulated bool // will simulate add and cancel orders if this is true } @@ -65,11 +66,12 @@ func makeKrakenExchange(apiKeys []api.ExchangeAPIKey, isSimulated bool) (api.Exc return &krakenExchange{ assetConverter: model.KrakenAssetConverter, assetConverterOpenOrders: model.KrakenAssetConverterOpenOrders, - apis: krakenAPIs, - apiNextIndex: 0, - delimiter: "", - withdrawKeys: asset2Address2Key{}, - isSimulated: isSimulated, + apis: krakenAPIs, + apiNextIndex: 0, + delimiter: "", + ocOverridesHandler: MakeEmptyOrderConstraintsOverridesHandler(), + withdrawKeys: asset2Address2Key{}, + isSimulated: isSimulated, }, nil } @@ -194,11 +196,21 @@ func getFieldValue(object krakenapi.BalanceResponse, fieldName string) float64 { // GetOrderConstraints impl func (k *krakenExchange) GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints { - constraints, ok := krakenPrecisionMatrix[*pair] - if !ok { - panic(fmt.Sprintf("krakenExchange could not find orderConstraints for trading pair %v. Try using the \"ccxt-kraken\" integration instead.", pair)) + oc, ok := krakenPrecisionMatrix[*pair] + if ok { + return k.ocOverridesHandler.Apply(pair, &oc) + } + + if k.ocOverridesHandler.IsCompletelyOverriden(pair) { + override := k.ocOverridesHandler.Get(pair) + return model.MakeOrderConstraintsFromOverride(override) } - return &constraints + panic(fmt.Sprintf("krakenExchange could not find orderConstraints for trading pair %v. Try using the \"ccxt-kraken\" integration instead.", pair)) +} + +// OverrideOrderConstraints impl, can partially override values for specific pairs +func (k *krakenExchange) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) { + k.ocOverridesHandler.Upsert(pair, override) } // GetAssetConverter impl. diff --git a/plugins/krakenExchange_test.go b/plugins/krakenExchange_test.go index d21e94c7d..9b09ae437 100644 --- a/plugins/krakenExchange_test.go +++ b/plugins/krakenExchange_test.go @@ -16,11 +16,12 @@ import ( var testKrakenExchange api.Exchange = &krakenExchange{ assetConverter: model.KrakenAssetConverter, assetConverterOpenOrders: model.KrakenAssetConverterOpenOrders, - apis: []*krakenapi.KrakenApi{krakenapi.New("", "")}, - apiNextIndex: 0, - delimiter: "", - withdrawKeys: asset2Address2Key{}, - isSimulated: true, + apis: []*krakenapi.KrakenApi{krakenapi.New("", "")}, + apiNextIndex: 0, + delimiter: "", + ocOverridesHandler: MakeEmptyOrderConstraintsOverridesHandler(), + withdrawKeys: asset2Address2Key{}, + isSimulated: true, } func TestGetTickerPrice(t *testing.T) { diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index bb5224159..5a65344ec 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -30,21 +30,28 @@ func (t *exchangeAPIKeysToml) toExchangeAPIKeys() []api.ExchangeAPIKey { // mirrorConfig contains the configuration params for this strategy type mirrorConfig struct { - Exchange string `valid:"-" toml:"EXCHANGE"` - ExchangeBase string `valid:"-" toml:"EXCHANGE_BASE"` - ExchangeQuote string `valid:"-" toml:"EXCHANGE_QUOTE"` - OrderbookDepth int32 `valid:"-" toml:"ORDERBOOK_DEPTH"` - VolumeDivideBy float64 `valid:"-" toml:"VOLUME_DIVIDE_BY"` - PerLevelSpread float64 `valid:"-" toml:"PER_LEVEL_SPREAD"` - MinBaseVolume float64 `valid:"-" toml:"MIN_BASE_VOLUME"` - OffsetTrades bool `valid:"-" toml:"OFFSET_TRADES"` - ExchangeAPIKeys exchangeAPIKeysToml `valid:"-" toml:"EXCHANGE_API_KEYS"` + Exchange string `valid:"-" toml:"EXCHANGE"` + ExchangeBase string `valid:"-" toml:"EXCHANGE_BASE"` + ExchangeQuote string `valid:"-" toml:"EXCHANGE_QUOTE"` + OrderbookDepth int32 `valid:"-" toml:"ORDERBOOK_DEPTH"` + VolumeDivideBy float64 `valid:"-" toml:"VOLUME_DIVIDE_BY"` + PerLevelSpread float64 `valid:"-" toml:"PER_LEVEL_SPREAD"` + PricePrecisionOverride *int8 `valid:"-" toml:"PRICE_PRECISION_OVERRIDE"` + VolumePrecisionOverride *int8 `valid:"-" toml:"VOLUME_PRECISION_OVERRIDE"` + MinBaseVolumeOverride *float64 `valid:"-" toml:"MIN_BASE_VOLUME_OVERRIDE"` + MinQuoteVolumeOverride *float64 `valid:"-" toml:"MIN_QUOTE_VOLUME_OVERRIDE"` + OffsetTrades bool `valid:"-" toml:"OFFSET_TRADES"` + ExchangeAPIKeys exchangeAPIKeysToml `valid:"-" toml:"EXCHANGE_API_KEYS"` } // String impl. func (c mirrorConfig) String() string { return utils.StructString(c, map[string]func(interface{}) interface{}{ - "EXCHANGE_API_KEYS": utils.Hide, + "EXCHANGE_API_KEYS": utils.Hide, + "PRICE_PRECISION_OVERRIDE": utils.UnwrapInt8Pointer, + "VOLUME_PRECISION_OVERRIDE": utils.UnwrapInt8Pointer, + "MIN_BASE_VOLUME_OVERRIDE": utils.UnwrapFloat64Pointer, + "MIN_QUOTE_VOLUME_OVERRIDE": utils.UnwrapFloat64Pointer, }) } @@ -102,8 +109,17 @@ func makeMirrorStrategy(sdex *SDEX, ieif *IEIF, pair *model.TradingPair, baseAss return nil, e } - if config.MinBaseVolume == 0.0 { - return nil, fmt.Errorf("need to specify non-zero MIN_BASE_VOLUME config param in mirror strategy config file") + if config.MinBaseVolumeOverride != nil && *config.MinBaseVolumeOverride <= 0.0 { + return nil, fmt.Errorf("need to specify positive MIN_BASE_VOLUME_OVERRIDE config param in mirror strategy config file") + } + if config.MinQuoteVolumeOverride != nil && *config.MinQuoteVolumeOverride <= 0.0 { + return nil, fmt.Errorf("need to specify positive MIN_QUOTE_VOLUME_OVERRIDE config param in mirror strategy config file") + } + if config.VolumePrecisionOverride != nil && *config.VolumePrecisionOverride < 0 { + return nil, fmt.Errorf("need to specify non-negative VOLUME_PRECISION_OVERRIDE config param in mirror strategy config file") + } + if config.PricePrecisionOverride != nil && *config.PricePrecisionOverride < 0 { + return nil, fmt.Errorf("need to specify non-negative PRICE_PRECISION_OVERRIDE config param in mirror strategy config file") } } else { exchange, e = MakeExchange(config.Exchange, simMode) @@ -119,10 +135,33 @@ func makeMirrorStrategy(sdex *SDEX, ieif *IEIF, pair *model.TradingPair, baseAss Base: exchange.GetAssetConverter().MustFromString(config.ExchangeBase), Quote: exchange.GetAssetConverter().MustFromString(config.ExchangeQuote), } - backingConstraints := exchange.GetOrderConstraints(backingPair) - if config.OffsetTrades { - backingConstraints.MinBaseVolume = *model.NumberFromFloat(config.MinBaseVolume, backingConstraints.VolumePrecision) + // update precision overrides + exchange.OverrideOrderConstraints(backingPair, model.MakeOrderConstraintsOverride( + config.PricePrecisionOverride, + config.VolumePrecisionOverride, + nil, + nil, + )) + if config.MinBaseVolumeOverride != nil { + // use updated precision overrides to convert the minBaseVolume to a model.Number + exchange.OverrideOrderConstraints(backingPair, model.MakeOrderConstraintsOverride( + nil, + nil, + model.NumberFromFloat(*config.MinBaseVolumeOverride, exchange.GetOrderConstraints(backingPair).VolumePrecision), + nil, + )) + } + if config.MinQuoteVolumeOverride != nil { + // use updated precision overrides to convert the minQuoteVolume to a model.Number + minQuoteVolume := model.NumberFromFloat(*config.MinQuoteVolumeOverride, exchange.GetOrderConstraints(backingPair).VolumePrecision) + exchange.OverrideOrderConstraints(backingPair, model.MakeOrderConstraintsOverride( + nil, + nil, + nil, + &minQuoteVolume, + )) } + backingConstraints := exchange.GetOrderConstraints(backingPair) log.Printf("primaryPair='%s', primaryConstraints=%s\n", pair, primaryConstraints) log.Printf("backingPair='%s', backingConstraints=%s\n", backingPair, backingConstraints) return &mirrorStrategy{ diff --git a/plugins/orderConstraintsOverridesHandler.go b/plugins/orderConstraintsOverridesHandler.go new file mode 100644 index 000000000..779ade8f2 --- /dev/null +++ b/plugins/orderConstraintsOverridesHandler.go @@ -0,0 +1,62 @@ +package plugins + +import "github.com/stellar/kelp/model" + +// OrderConstraintsOverridesHandler knows how to capture overrides and apply them onto OrderConstraints +type OrderConstraintsOverridesHandler struct { + overrides map[string]*model.OrderConstraintsOverride +} + +// MakeEmptyOrderConstraintsOverridesHandler is a factory method +func MakeEmptyOrderConstraintsOverridesHandler() *OrderConstraintsOverridesHandler { + return &OrderConstraintsOverridesHandler{ + overrides: map[string]*model.OrderConstraintsOverride{}, + } +} + +// MakeOrderConstraintsOverridesHandler is a factory method +func MakeOrderConstraintsOverridesHandler(inputs map[model.TradingPair]model.OrderConstraints) *OrderConstraintsOverridesHandler { + overrides := map[string]*model.OrderConstraintsOverride{} + for p, oc := range inputs { + overrides[p.String()] = model.MakeOrderConstraintsOverrideFromConstraints(&oc) + } + + return &OrderConstraintsOverridesHandler{ + overrides: overrides, + } +} + +// Apply creates a new order constraints after checking for any existing overrides +func (ocHandler *OrderConstraintsOverridesHandler) Apply(pair *model.TradingPair, oc *model.OrderConstraints) *model.OrderConstraints { + override, has := ocHandler.overrides[pair.String()] + if !has { + return oc + } + return model.MakeOrderConstraintsWithOverride(*oc, override) +} + +// Get impl, panics if the override does not exist +func (ocHandler *OrderConstraintsOverridesHandler) Get(pair *model.TradingPair) *model.OrderConstraintsOverride { + return ocHandler.overrides[pair.String()] +} + +// Upsert allows you to set overrides to partially override values for specific pairs +func (ocHandler *OrderConstraintsOverridesHandler) Upsert(pair *model.TradingPair, override *model.OrderConstraintsOverride) { + existingOverride, exists := ocHandler.overrides[pair.String()] + if !exists { + ocHandler.overrides[pair.String()] = override + return + } + + existingOverride.Augment(override) + ocHandler.overrides[pair.String()] = existingOverride +} + +// IsCompletelyOverriden returns true if the override exists and is complete for the given trading pair +func (ocHandler *OrderConstraintsOverridesHandler) IsCompletelyOverriden(pair *model.TradingPair) bool { + override, has := ocHandler.overrides[pair.String()] + if !has { + return false + } + return override.IsComplete() +} diff --git a/plugins/sdex.go b/plugins/sdex.go index c8aba18de..a397b510c 100644 --- a/plugins/sdex.go +++ b/plugins/sdex.go @@ -46,9 +46,10 @@ type SDEX struct { tradingOnSdex bool // uninitialized - seqNum uint64 - reloadSeqNum bool - ieif *IEIF + seqNum uint64 + reloadSeqNum bool + ieif *IEIF + ocOverridesHandler *OrderConstraintsOverridesHandler } // enforce SDEX implements api.Constrainable @@ -93,11 +94,12 @@ func MakeSDEX( threadTracker: threadTracker, operationalBuffer: operationalBuffer, operationalBufferNonNativePct: operationalBufferNonNativePct, - simMode: simMode, - pair: pair, - assetMap: assetMap, - opFeeStroopsFn: opFeeStroopsFn, - tradingOnSdex: exchangeShim == nil, + simMode: simMode, + pair: pair, + assetMap: assetMap, + opFeeStroopsFn: opFeeStroopsFn, + tradingOnSdex: exchangeShim == nil, + ocOverridesHandler: MakeEmptyOrderConstraintsOverridesHandler(), } if exchangeShim == nil { @@ -165,7 +167,12 @@ func (sdex *SDEX) incrementSeqNum() { // GetOrderConstraints impl func (sdex *SDEX) GetOrderConstraints(pair *model.TradingPair) *model.OrderConstraints { - return sdexOrderConstraints + return sdex.ocOverridesHandler.Apply(pair, sdexOrderConstraints) +} + +// OverrideOrderConstraints impl, can partially override values for specific pairs +func (sdex *SDEX) OverrideOrderConstraints(pair *model.TradingPair, override *model.OrderConstraintsOverride) { + sdex.ocOverridesHandler.Upsert(pair, override) } // DeleteAllOffers is a helper that accumulates delete operations for the passed in offers diff --git a/support/utils/configs.go b/support/utils/configs.go index 77ba181f6..c796f4e74 100644 --- a/support/utils/configs.go +++ b/support/utils/configs.go @@ -79,3 +79,21 @@ func passthrough(i interface{}) interface{} { func Hide(i interface{}) interface{} { return "" } + +// UnwrapFloat64Pointer unwraps a float64 pointer +func UnwrapFloat64Pointer(i interface{}) interface{} { + p := i.(*float64) + if p == nil { + return "" + } + return *p +} + +// UnwrapInt8Pointer unwraps a int8 pointer +func UnwrapInt8Pointer(i interface{}) interface{} { + p := i.(*int8) + if p == nil { + return "" + } + return *p +} diff --git a/trader/config.go b/trader/config.go index 5ec0a68e5..03a8cbd4a 100644 --- a/trader/config.go +++ b/trader/config.go @@ -19,31 +19,34 @@ type FeeConfig struct { // BotConfig represents the configuration params for the bot type BotConfig struct { - SourceSecretSeed string `valid:"-" toml:"SOURCE_SECRET_SEED"` - TradingSecretSeed string `valid:"-" toml:"TRADING_SECRET_SEED"` - AssetCodeA string `valid:"-" toml:"ASSET_CODE_A"` - IssuerA string `valid:"-" toml:"ISSUER_A"` - AssetCodeB string `valid:"-" toml:"ASSET_CODE_B"` - IssuerB string `valid:"-" toml:"ISSUER_B"` - TickIntervalSeconds int32 `valid:"-" toml:"TICK_INTERVAL_SECONDS"` - MaxTickDelayMillis int64 `valid:"-" toml:"MAX_TICK_DELAY_MILLIS"` - DeleteCyclesThreshold int64 `valid:"-" toml:"DELETE_CYCLES_THRESHOLD"` - SubmitMode string `valid:"-" toml:"SUBMIT_MODE"` - FillTrackerSleepMillis uint32 `valid:"-" toml:"FILL_TRACKER_SLEEP_MILLIS"` - FillTrackerDeleteCyclesThreshold int64 `valid:"-" toml:"FILL_TRACKER_DELETE_CYCLES_THRESHOLD"` - HorizonURL string `valid:"-" toml:"HORIZON_URL"` - Fee *FeeConfig `valid:"-" toml:"FEE"` - MinCentralizedBaseVolume float64 `valid:"-" toml:"MIN_CENTRALIZED_BASE_VOLUME"` - AlertType string `valid:"-" toml:"ALERT_TYPE"` - AlertAPIKey string `valid:"-" toml:"ALERT_API_KEY"` - MonitoringPort uint16 `valid:"-" toml:"MONITORING_PORT"` - MonitoringTLSCert string `valid:"-" toml:"MONITORING_TLS_CERT"` - MonitoringTLSKey string `valid:"-" toml:"MONITORING_TLS_KEY"` - GoogleClientID string `valid:"-" toml:"GOOGLE_CLIENT_ID"` - GoogleClientSecret string `valid:"-" toml:"GOOGLE_CLIENT_SECRET"` - AcceptableEmails string `valid:"-" toml:"ACCEPTABLE_GOOGLE_EMAILS"` - TradingExchange string `valid:"-" toml:"TRADING_EXCHANGE"` - ExchangeAPIKeys []struct { + SourceSecretSeed string `valid:"-" toml:"SOURCE_SECRET_SEED"` + TradingSecretSeed string `valid:"-" toml:"TRADING_SECRET_SEED"` + AssetCodeA string `valid:"-" toml:"ASSET_CODE_A"` + IssuerA string `valid:"-" toml:"ISSUER_A"` + AssetCodeB string `valid:"-" toml:"ASSET_CODE_B"` + IssuerB string `valid:"-" toml:"ISSUER_B"` + TickIntervalSeconds int32 `valid:"-" toml:"TICK_INTERVAL_SECONDS"` + MaxTickDelayMillis int64 `valid:"-" toml:"MAX_TICK_DELAY_MILLIS"` + DeleteCyclesThreshold int64 `valid:"-" toml:"DELETE_CYCLES_THRESHOLD"` + SubmitMode string `valid:"-" toml:"SUBMIT_MODE"` + FillTrackerSleepMillis uint32 `valid:"-" toml:"FILL_TRACKER_SLEEP_MILLIS"` + FillTrackerDeleteCyclesThreshold int64 `valid:"-" toml:"FILL_TRACKER_DELETE_CYCLES_THRESHOLD"` + HorizonURL string `valid:"-" toml:"HORIZON_URL"` + Fee *FeeConfig `valid:"-" toml:"FEE"` + CentralizedPricePrecisionOverride *int8 `valid:"-" toml:"CENTRALIZED_PRICE_PRECISION_OVERRIDE"` + CentralizedVolumePrecisionOverride *int8 `valid:"-" toml:"CENTRALIZED_VOLUME_PRECISION_OVERRIDE"` + CentralizedMinBaseVolumeOverride *float64 `valid:"-" toml:"CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE"` + CentralizedMinQuoteVolumeOverride *float64 `valid:"-" toml:"CENTRALIZED_MIN_QUOTE_VOLUME_OVERRIDE"` + AlertType string `valid:"-" toml:"ALERT_TYPE"` + AlertAPIKey string `valid:"-" toml:"ALERT_API_KEY"` + MonitoringPort uint16 `valid:"-" toml:"MONITORING_PORT"` + MonitoringTLSCert string `valid:"-" toml:"MONITORING_TLS_CERT"` + MonitoringTLSKey string `valid:"-" toml:"MONITORING_TLS_KEY"` + GoogleClientID string `valid:"-" toml:"GOOGLE_CLIENT_ID"` + GoogleClientSecret string `valid:"-" toml:"GOOGLE_CLIENT_SECRET"` + AcceptableEmails string `valid:"-" toml:"ACCEPTABLE_GOOGLE_EMAILS"` + TradingExchange string `valid:"-" toml:"TRADING_EXCHANGE"` + ExchangeAPIKeys []struct { Key string `valid:"-" toml:"KEY"` Secret string `valid:"-" toml:"SECRET"` } `valid:"-" toml:"EXCHANGE_API_KEYS"` @@ -59,13 +62,17 @@ type BotConfig struct { // String impl. func (b BotConfig) String() string { return utils.StructString(b, map[string]func(interface{}) interface{}{ - "EXCHANGE_API_KEYS": utils.Hide, - "SOURCE_SECRET_SEED": utils.SecretKey2PublicKey, - "TRADING_SECRET_SEED": utils.SecretKey2PublicKey, - "ALERT_API_KEY": utils.Hide, - "GOOGLE_CLIENT_ID": utils.Hide, - "GOOGLE_CLIENT_SECRET": utils.Hide, - "ACCEPTABLE_GOOGLE_EMAILS": utils.Hide, + "EXCHANGE_API_KEYS": utils.Hide, + "SOURCE_SECRET_SEED": utils.SecretKey2PublicKey, + "TRADING_SECRET_SEED": utils.SecretKey2PublicKey, + "ALERT_API_KEY": utils.Hide, + "GOOGLE_CLIENT_ID": utils.Hide, + "GOOGLE_CLIENT_SECRET": utils.Hide, + "ACCEPTABLE_GOOGLE_EMAILS": utils.Hide, + "CENTRALIZED_PRICE_PRECISION_OVERRIDE": utils.UnwrapInt8Pointer, + "CENTRALIZED_VOLUME_PRECISION_OVERRIDE": utils.UnwrapInt8Pointer, + "CENTRALIZED_MIN_BASE_VOLUME_OVERRIDE": utils.UnwrapFloat64Pointer, + "CENTRALIZED_MIN_QUOTE_VOLUME_OVERRIDE": utils.UnwrapFloat64Pointer, }) } diff --git a/trader/trader.go b/trader/trader.go index 4b74fe1fb..ff691b93d 100644 --- a/trader/trader.go +++ b/trader/trader.go @@ -55,7 +55,6 @@ func MakeBot( assetBase horizon.Asset, assetQuote horizon.Asset, tradingPair *model.TradingPair, - minBaseVolume *float64, tradingAccount string, sdex *plugins.SDEX, exchangeShim api.ExchangeShim, @@ -68,15 +67,9 @@ func MakeBot( dataKey *model.BotKey, alert api.Alert, ) *Trader { - submitFilters := []plugins.SubmitFilter{} - - oc := exchangeShim.GetOrderConstraints(tradingPair) - if minBaseVolume != nil { - oc.MinBaseVolume = *model.NumberFromFloat(*minBaseVolume, oc.VolumePrecision) + submitFilters := []plugins.SubmitFilter{ + plugins.MakeFilterOrderConstraints(exchangeShim.GetOrderConstraints(tradingPair), assetBase, assetQuote), } - orderConstraintsFilter := plugins.MakeFilterOrderConstraints(oc, assetBase, assetQuote) - submitFilters = append(submitFilters, orderConstraintsFilter) - sdexSubmitFilter := plugins.MakeFilterMakerMode(submitMode, exchangeShim, sdex, tradingPair) if sdexSubmitFilter != nil { submitFilters = append(submitFilters, sdexSubmitFilter) @@ -170,6 +163,12 @@ func (t *Trader) update() { t.load() t.loadExistingOffers() + pair := &model.TradingPair{ + Base: model.FromHorizonAsset(t.assetBase), + Quote: model.FromHorizonAsset(t.assetQuote), + } + log.Printf("orderConstraints for trading pair %s: %s", pair, t.exchangeShim.GetOrderConstraints(pair)) + // TODO 2 streamline the request data instead of caching // reset cache of balances for this update cycle to reduce redundant requests to calculate asset balances t.sdex.IEIF().ResetCachedBalances() From 860d76b0c089efa62299093ff9ccf2d7b868a14c Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 17:35:27 -0700 Subject: [PATCH 16/26] google auth for /metrics endpoint should depend on config values, fixes #151 --- cmd/trade.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/cmd/trade.go b/cmd/trade.go index 7a7c579ca..b9a2a88f6 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -467,34 +467,36 @@ func runTradeCmd(options inputs) { } func startMonitoringServer(l logger.Logger, botConfig trader.BotConfig) error { - serverConfig := &networking.Config{ - GoogleClientID: botConfig.GoogleClientID, - GoogleClientSecret: botConfig.GoogleClientSecret, - PermittedEmails: map[string]bool{}, - } - // Load acceptable Google emails into the map - for _, email := range strings.Split(botConfig.AcceptableEmails, ",") { - serverConfig.PermittedEmails[email] = true - } - healthMetrics, e := monitoring.MakeMetricsRecorder(map[string]interface{}{"success": true}) if e != nil { - return fmt.Errorf("unable to make metrics recorder for the health endpoint: %s", e) + return fmt.Errorf("unable to make metrics recorder for the /health endpoint: %s", e) } - healthEndpoint, e := monitoring.MakeMetricsEndpoint("/health", healthMetrics, networking.NoAuth) if e != nil { return fmt.Errorf("unable to make /health endpoint: %s", e) } + kelpMetrics, e := monitoring.MakeMetricsRecorder(nil) if e != nil { return fmt.Errorf("unable to make metrics recorder for the /metrics endpoint: %s", e) } - - metricsEndpoint, e := monitoring.MakeMetricsEndpoint("/metrics", kelpMetrics, networking.GoogleAuth) + metricsAuth := networking.NoAuth + if botConfig.GoogleClientID != "" || botConfig.GoogleClientSecret != "" { + metricsAuth = networking.GoogleAuth + } + metricsEndpoint, e := monitoring.MakeMetricsEndpoint("/metrics", kelpMetrics, metricsAuth) if e != nil { return fmt.Errorf("unable to make /metrics endpoint: %s", e) } + + serverConfig := &networking.Config{ + GoogleClientID: botConfig.GoogleClientID, + GoogleClientSecret: botConfig.GoogleClientSecret, + PermittedEmails: map[string]bool{}, + } + for _, email := range strings.Split(botConfig.AcceptableEmails, ",") { + serverConfig.PermittedEmails[email] = true + } server, e := networking.MakeServer(serverConfig, []networking.Endpoint{healthEndpoint, metricsEndpoint}) if e != nil { return fmt.Errorf("unable to initialize the metrics server: %s", e) From 7edc99c8e8be5a9b82fc0225d491c5c2613e18b3 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:12:40 -0700 Subject: [PATCH 17/26] update comment on api.ExchangeParams --- api/exchange.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/exchange.go b/api/exchange.go index ba9c5deff..7073cee06 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -14,7 +14,7 @@ type ExchangeAPIKey struct { Secret string } -// ExchangeParam specifies an additional ccxt parameter +// ExchangeParam specifies an additional parameter to be sent when initializing the exchange type ExchangeParam struct { Parameter string Value string From f42d8320c262c060c89a6513b79768d9bdce64e6 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:13:39 -0700 Subject: [PATCH 18/26] update comment on sample_mirror.cfg --- examples/configs/trader/sample_mirror.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/configs/trader/sample_mirror.cfg b/examples/configs/trader/sample_mirror.cfg index 8858b9378..377b3044c 100644 --- a/examples/configs/trader/sample_mirror.cfg +++ b/examples/configs/trader/sample_mirror.cfg @@ -49,12 +49,12 @@ MIN_BASE_VOLUME=30.0 #KEY="" #SECRET="" -# if your ccxt exchange requires additional parameters, list them here with the the necessary values +# if your exchange requires additional parameters, list them here with the the necessary values (only ccxt supported currently) # [[EXCHANGE_PARAMS]] # PARAMETER="" # VALUE="" -# if your exchange requires additional headers, list them here with the the necessary values +# if your exchange requires additional headers, list them here with the the necessary values (only ccxt supported currently) # [[EXCHANGE_HEADERS]] # HEADER="" -# VALUE="" \ No newline at end of file +# VALUE="" From caa9fecb71dcb3c5e25e9d9682a745245e986ffc Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:15:45 -0700 Subject: [PATCH 19/26] update sample trader cfg --- examples/configs/trader/sample_trader.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/configs/trader/sample_trader.cfg b/examples/configs/trader/sample_trader.cfg index 3aa91a7c7..cedeb6062 100644 --- a/examples/configs/trader/sample_trader.cfg +++ b/examples/configs/trader/sample_trader.cfg @@ -100,8 +100,8 @@ MAX_OP_FEE_STROOPS=5000 #KEY="" #SECRET="" -# some ccxt exchanges require additional parameters, e.g. coinbase pro requires a "password" -# if that parameter is incorporated into ccxt itself, list it here +# if your exchange requires additional parameters during initialization, list them here (only ccxt supported currently) +# Note that some CCXT exchanges require additional parameters, e.g. coinbase pro requires a "password" #[[EXCHANGE_PARAMS]] #PARAMETER="" #VALUE="" @@ -109,10 +109,10 @@ MAX_OP_FEE_STROOPS=5000 #PARAMETER="" #VALUE="" -# if your exchange requires additional parameters as http headers that are not incorporated into ccxt, list them here +# if your exchange requires additional parameters as http headers, list them here (only ccxt supported currently) #[[EXCHANGE_HEADERS]] #HEADER="" #VALUE="" #[[EXCHANGE_HEADERS]] #HEADER="" -#VALUE="" \ No newline at end of file +#VALUE="" From 967f5c3ce7062aeb942e46469c9bf2f72dda0c8b Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:18:04 -0700 Subject: [PATCH 20/26] Update ccxtExchange_test.go --- plugins/ccxtExchange_test.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugins/ccxtExchange_test.go b/plugins/ccxtExchange_test.go index f93fc92ac..4c36b4f76 100644 --- a/plugins/ccxtExchange_test.go +++ b/plugins/ccxtExchange_test.go @@ -17,7 +17,6 @@ var emptyAPIKey = api.ExchangeAPIKey{} var emptyParams = api.ExchangeParam{} var supportedTradingExchanges = map[string]api.ExchangeAPIKey{ "binance": {}, - // "coinbasepro": {}, } var testOrderConstraints = map[model.TradingPair]model.OrderConstraints{ @@ -444,6 +443,7 @@ func TestCancelOrder_Ccxt(t *testing.T) { } func TestGetOrderConstraints_Ccxt_Precision(t *testing.T) { + // coinbasepro gives incorrect precision values so we do not test it here testCases := []struct { exchangeName string pair *model.TradingPair @@ -471,13 +471,6 @@ func TestGetOrderConstraints_Ccxt_Precision(t *testing.T) { wantPricePrecision: 8, wantVolPrecision: 0, }, - // disabled until volume precision is fixed - // { - // exchangeName: "coinbasepro", - // pair: &model.TradingPair{Base: model.XLM, Quote: model.USD}, - // wantPricePrecision: 6, - // wantVolPrecision: 0, - // }, } for _, kase := range testCases { From 1a912c03ad6beee6854ce4ec78823132559e4775 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:22:51 -0700 Subject: [PATCH 21/26] Update factory.go --- plugins/factory.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/factory.go b/plugins/factory.go index 99a8dcedd..a8c66359c 100644 --- a/plugins/factory.go +++ b/plugins/factory.go @@ -255,8 +255,6 @@ func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, exch return nil, fmt.Errorf("cannot make trading exchange, apiKeys mising") } - fmt.Printf("MakeTradingExchange received exchange headers: %s\n", headers) - x, e := exchange.makeFn(exchangeFactoryData{ simMode: simMode, apiKeys: apiKeys, From a88cb348eadee9832ce48d9e9b45ad5499ab05e2 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:41:07 -0700 Subject: [PATCH 22/26] CXT_PARAMS -> EXCHANGE_PARAMS in mirror config --- plugins/mirrorStrategy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index 22e916e3e..02014b687 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -71,7 +71,7 @@ type mirrorConfig struct { MinBaseVolume float64 `valid:"-" toml:"MIN_BASE_VOLUME"` OffsetTrades bool `valid:"-" toml:"OFFSET_TRADES"` ExchangeAPIKeys exchangeAPIKeysToml `valid:"-" toml:"EXCHANGE_API_KEYS"` - ExchangeParams exchangeParamsToml `valid:"-" toml:"CCXT_PARAMS"` + ExchangeParams exchangeParamsToml `valid:"-" toml:"EXCHANGE_PARAMS"` ExchangeHeaders exchangeHeadersToml `valid:"-" toml:"EXCHANGE_HEADERS"` } From 46d2869552a9f894cf065d33b9b948a28c1208c2 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:42:24 -0700 Subject: [PATCH 23/26] follow toml var name convention in mirrorStrategy --- plugins/mirrorStrategy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index 02014b687..d9826c967 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -33,9 +33,9 @@ type exchangeParamsToml []struct { Value string `valid:"-" toml:"VALUE"` } -func (c *exchangeParamsToml) toExchangeParams() []api.ExchangeParam { +func (t *exchangeParamsToml) toExchangeParams() []api.ExchangeParam { exchangeParams := []api.ExchangeParam{} - for _, param := range *c { + for _, param := range *t { exchangeParams = append(exchangeParams, api.ExchangeParam{ Parameter: param.Parameter, Value: param.Value, @@ -49,9 +49,9 @@ type exchangeHeadersToml []struct { Value string `valid:"-" toml:"VALUE"` } -func (e *exchangeHeadersToml) toExchangeHeaders() []api.ExchangeHeader { +func (t *exchangeHeadersToml) toExchangeHeaders() []api.ExchangeHeader { apiHeaders := []api.ExchangeHeader{} - for _, header := range *e { + for _, header := range *t { apiHeaders = append(apiHeaders, api.ExchangeHeader{ Header: header.Header, Value: header.Value, From 2b1a180e25b12c74b064efa526ebbc41f994b875 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:45:31 -0700 Subject: [PATCH 24/26] code style ccxt.go --- support/sdk/ccxt.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index 8f0d4c038..201e34218 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -190,18 +190,15 @@ func (c *Ccxt) hasInstance(instanceList []string) bool { } func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey, params []api.ExchangeParam) error { - data := map[string]string{} - - data["id"] = c.instanceName - data["apiKey"] = apiKey.Key - data["secret"] = apiKey.Secret - + data := map[string]string{ + "id": c.instanceName, + "apiKey": apiKey.Key, + "secret": apiKey.Secret, + } for _, param := range params { data[param.Parameter] = param.Value } - jsonData, e := json.Marshal(data) - if e != nil { return fmt.Errorf("error marshaling instanceName '%s' as ID for exchange '%s': %s", c.instanceName, c.exchangeName, e) } From db16ab0a6c372410db4a45f4af5e3b721aac01f9 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:49:11 -0700 Subject: [PATCH 25/26] hide exchange params and headers in mirror config output --- plugins/mirrorStrategy.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index d9826c967..2134ddcd4 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -79,6 +79,8 @@ type mirrorConfig struct { func (c mirrorConfig) String() string { return utils.StructString(c, map[string]func(interface{}) interface{}{ "EXCHANGE_API_KEYS": utils.Hide, + "EXCHANGE_PARAMS": utils.Hide, + "EXCHANGE_HEADERS": utils.Hide, }) } From af79342c10c7a1a93e51d446a373822c66c0dd4d Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Wed, 10 Apr 2019 19:05:33 -0700 Subject: [PATCH 26/26] rename parameter to param and PARAMETER to PARAM --- api/exchange.go | 4 ++-- cmd/trade.go | 4 ++-- examples/configs/trader/sample_mirror.cfg | 12 ++++++------ examples/configs/trader/sample_trader.cfg | 4 ++-- plugins/mirrorStrategy.go | 8 ++++---- support/sdk/ccxt.go | 4 ++-- trader/config.go | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/exchange.go b/api/exchange.go index 0e78f0ef2..dc0c3cc2d 100644 --- a/api/exchange.go +++ b/api/exchange.go @@ -16,8 +16,8 @@ type ExchangeAPIKey struct { // ExchangeParam specifies an additional parameter to be sent when initializing the exchange type ExchangeParam struct { - Parameter string - Value string + Param string + Value string } // ExchangeHeader specifies additional HTTP headers diff --git a/cmd/trade.go b/cmd/trade.go index 8ed5e1e89..f63b7876e 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -208,8 +208,8 @@ func makeExchangeShimSdex( exchangeParams := []api.ExchangeParam{} for _, param := range botConfig.ExchangeParams { exchangeParams = append(exchangeParams, api.ExchangeParam{ - Parameter: param.Parameter, - Value: param.Value, + Param: param.Param, + Value: param.Value, }) } diff --git a/examples/configs/trader/sample_mirror.cfg b/examples/configs/trader/sample_mirror.cfg index 1b4398930..6a853b10f 100644 --- a/examples/configs/trader/sample_mirror.cfg +++ b/examples/configs/trader/sample_mirror.cfg @@ -56,11 +56,11 @@ PER_LEVEL_SPREAD=0.005 #SECRET="" # if your exchange requires additional parameters, list them here with the the necessary values (only ccxt supported currently) -# [[EXCHANGE_PARAMS]] -# PARAMETER="" -# VALUE="" +#[[EXCHANGE_PARAMS]] +#PARAM="" +#VALUE="" # if your exchange requires additional headers, list them here with the the necessary values (only ccxt supported currently) -# [[EXCHANGE_HEADERS]] -# HEADER="" -# VALUE="" +#[[EXCHANGE_HEADERS]] +#HEADER="" +#VALUE="" diff --git a/examples/configs/trader/sample_trader.cfg b/examples/configs/trader/sample_trader.cfg index 208a68480..b2e48ccaf 100644 --- a/examples/configs/trader/sample_trader.cfg +++ b/examples/configs/trader/sample_trader.cfg @@ -110,10 +110,10 @@ MAX_OP_FEE_STROOPS=5000 # if your exchange requires additional parameters during initialization, list them here (only ccxt supported currently) # Note that some CCXT exchanges require additional parameters, e.g. coinbase pro requires a "password" #[[EXCHANGE_PARAMS]] -#PARAMETER="" +#PARAM="" #VALUE="" #[[EXCHANGE_PARAMS]] -#PARAMETER="" +#PARAM="" #VALUE="" # if your exchange requires additional parameters as http headers, list them here (only ccxt supported currently) diff --git a/plugins/mirrorStrategy.go b/plugins/mirrorStrategy.go index c8c4bd22d..5b1efe64b 100644 --- a/plugins/mirrorStrategy.go +++ b/plugins/mirrorStrategy.go @@ -29,16 +29,16 @@ func (t *exchangeAPIKeysToml) toExchangeAPIKeys() []api.ExchangeAPIKey { } type exchangeParamsToml []struct { - Parameter string `valid:"-" toml:"PARAMETER"` - Value string `valid:"-" toml:"VALUE"` + Param string `valid:"-" toml:"PARAM"` + Value string `valid:"-" toml:"VALUE"` } func (t *exchangeParamsToml) toExchangeParams() []api.ExchangeParam { exchangeParams := []api.ExchangeParam{} for _, param := range *t { exchangeParams = append(exchangeParams, api.ExchangeParam{ - Parameter: param.Parameter, - Value: param.Value, + Param: param.Param, + Value: param.Value, }) } return exchangeParams diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index 201e34218..219c65298 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -191,12 +191,12 @@ func (c *Ccxt) hasInstance(instanceList []string) bool { func (c *Ccxt) newInstance(apiKey api.ExchangeAPIKey, params []api.ExchangeParam) error { data := map[string]string{ - "id": c.instanceName, + "id": c.instanceName, "apiKey": apiKey.Key, "secret": apiKey.Secret, } for _, param := range params { - data[param.Parameter] = param.Value + data[param.Param] = param.Value } jsonData, e := json.Marshal(data) if e != nil { diff --git a/trader/config.go b/trader/config.go index 7d3bd2c48..3730293d0 100644 --- a/trader/config.go +++ b/trader/config.go @@ -51,8 +51,8 @@ type BotConfig struct { Secret string `valid:"-" toml:"SECRET"` } `valid:"-" toml:"EXCHANGE_API_KEYS"` ExchangeParams []struct { - Parameter string `valid:"-" toml:"PARAMETER"` - Value string `valid:"-" toml:"VALUE"` + Param string `valid:"-" toml:"PARAM"` + Value string `valid:"-" toml:"VALUE"` } `valid:"-" toml:"EXCHANGE_PARAMS"` ExchangeHeaders []struct { Header string `valid:"-" toml:"HEADER"`