From 55ea1fe4341c71803152dccb699a9afca055c974 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 25 Aug 2017 15:54:45 +1000 Subject: [PATCH 01/32] Add generalised functions for handling exchange enabled and available currencies --- config/config.go | 30 +-- config_example.dat | 212 +++++++++++++---- currency/pair/pair.go | 16 +- currency/pair/pair_test.go | 20 ++ exchanges/anx/anx.go | 10 + exchanges/anx/anx_types.go | 9 +- exchanges/anx/anx_wrapper.go | 66 +++++- exchanges/bitfinex/bitfinex.go | 8 + exchanges/bitfinex/bitfinex_wrapper.go | 7 +- exchanges/bitstamp/bitstamp.go | 8 + exchanges/bitstamp/bitstamp_wrapper.go | 5 +- exchanges/bittrex/bittrex.go | 8 + exchanges/bittrex/bittrex_wrapper.go | 29 ++- exchanges/btcc/btcc.go | 8 + exchanges/btcc/btcc_wrapper.go | 9 +- exchanges/btce/btce.go | 10 +- exchanges/btce/btce_wrapper.go | 19 +- exchanges/btcmarkets/btcmarkets.go | 9 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 33 ++- exchanges/coinut/coinut.go | 8 + exchanges/coinut/coinut_wrapper.go | 11 +- exchanges/exchange.go | 219 ++++++++++++++++-- exchanges/exchange_test.go | 113 ++++++++- exchanges/gdax/gdax.go | 8 + exchanges/gdax/gdax_wrapper.go | 12 +- exchanges/gemini/gemini.go | 8 + exchanges/gemini/gemini_wrapper.go | 7 +- exchanges/huobi/huobi.go | 8 + exchanges/huobi/huobi_wrapper.go | 5 +- exchanges/itbit/itbit.go | 8 + exchanges/itbit/itbit_wrapper.go | 5 +- exchanges/kraken/kraken.go | 9 + exchanges/kraken/kraken_wrapper.go | 20 +- exchanges/lakebtc/lakebtc.go | 8 + exchanges/lakebtc/lakebtc_wrapper.go | 5 +- exchanges/liqui/liqui.go | 9 + exchanges/liqui/liqui_wrapper.go | 22 +- exchanges/localbitcoins/localbitcoins.go | 8 + .../localbitcoins/localbitcoins_wrapper.go | 5 +- exchanges/okcoin/okcoin.go | 13 ++ exchanges/okcoin/okcoin_wrapper.go | 12 +- exchanges/poloniex/poloniex.go | 9 + exchanges/poloniex/poloniex_wrapper.go | 5 +- testdata/configtest.dat | 177 +++++++++++--- ticker_routes.go | 10 +- 45 files changed, 1032 insertions(+), 208 deletions(-) diff --git a/config/config.go b/config/config.go index 1409efbe..531d3127 100644 --- a/config/config.go +++ b/config/config.go @@ -78,7 +78,9 @@ type Post struct { // CurrencyPairFormatConfig stores the users preferred currency pair display type CurrencyPairFormatConfig struct { Uppercase bool - Delimiter string + Delimiter string `json:",omitempty"` + Separator string `json:",omitempty"` + Index string `json:",omitempty"` } // Config is the overarching object that holds all the information for @@ -96,18 +98,20 @@ type Config struct { // ExchangeConfig holds all the information needed for each enabled Exchange. type ExchangeConfig struct { - Name string - Enabled bool - Verbose bool - Websocket bool - RESTPollingDelay time.Duration - AuthenticatedAPISupport bool - APIKey string - APISecret string - ClientID string `json:",omitempty"` - AvailablePairs string - EnabledPairs string - BaseCurrencies string + Name string + Enabled bool + Verbose bool + Websocket bool + RESTPollingDelay time.Duration + AuthenticatedAPISupport bool + APIKey string + APISecret string + ClientID string `json:",omitempty"` + AvailablePairs string + EnabledPairs string + BaseCurrencies string + ConfigCurrencyPairFormat *CurrencyPairFormatConfig `json:"ConfigCurrencyPairFormat"` + RequestCurrencyPairFormat *CurrencyPairFormatConfig `json:"RequestCurrencyPairFormat"` } // GetConfigEnabledExchanges returns the number of exchanges that are enabled. diff --git a/config_example.dat b/config_example.dat index 65d01df0..401ce0ad 100644 --- a/config_example.dat +++ b/config_example.dat @@ -11,22 +11,26 @@ { "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", "CoinType": "BTC", - "Balance": 124178.0002442 + "Balance": 124178.00647714, + "Description": "" }, { "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", "CoinType": "BTC", - "Balance": 103439.83659727 + "Balance": 107843.84030984, + "Description": "" }, { "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", "CoinType": "LTC", - "Balance": 3.00000005e+06 + "Balance": 100000.052, + "Description": "" }, { "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", "CoinType": "ETH", - "Balance": 5.774999820458524e+06 + "Balance": 3.224999915984445e+24, + "Description": "" } ] }, @@ -58,10 +62,17 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", "EnabledPairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", - "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD" + "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Index": "BTC" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Index": "BTC" + } }, { "Name": "Bitfinex", @@ -72,10 +83,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", - "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,BFXUSD,BFXBTC,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC", + "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BCCBTC,BCUBTC,BCCUSD,BCUUSD,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH", "EnabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Bitstamp", @@ -89,7 +105,13 @@ "ClientID": "ClientID", "AvailablePairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "EnabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", - "BaseCurrencies": "USD,EUR" + "BaseCurrencies": "USD,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Bittrex", @@ -100,10 +122,17 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", - "AvailablePairs": "BTCLTC,BTCDOGE,BTCVTC,BTCPPC,BTCFTC,BTCRDD,BTCNXT,BTCDASH,BTCPOT,BTCBLK,BTCEMC2,BTCXMY,BTCAUR,BTCEFL,BTCGLD,BTCSLR,BTCPTC,BTCGRS,BTCNLG,BTCRBY,BTCXWC,BTCMONA,BTCTHC,BTCENRG,BTCERC,BTCNAUT,BTCVRC,BTCCURE,BTCXBB,BTCXMR,BTCCLOAK,BTCSTART,BTCKORE,BTCXDN,BTCTRUST,BTCNAV,BTCXST,BTCBTCD,BTCVIA,BTCUNO,BTCPINK,BTCIOC,BTCCANN,BTCSYS,BTCNEOS,BTCDGB,BTCBURST,BTCEXCL,BTCSWIFT,BTCDOPE,BTCBLOCK,BTCABY,BTCBYC,BTCXMG,BTCBLITZ,BTCBAY,BTCBTS,BTCFAIR,BTCSPR,BTCVTR,BTCXRP,BTCGAME,BTCCOVAL,BTCNXS,BTCXCP,BTCBITB,BTCGEO,BTCFLDC,BTCGRC,BTCFLO,BTCNBT,BTCMUE,BTCXEM,BTCCLAM,BTCDMD,BTCGAM,BTCSPHR,BTCOK,BTCSNRG,BTCPKB,BTCCPC,BTCAEON,BTCETH,BTCGCR,BTCTX,BTCBCY,BTCEXP,BTCINFX,BTCOMNI,BTCAMP,BTCAGRS,BTCXLM,BTCBTA,USDTBTC,BITCNYBTC,BTCCLUB,BTCVOX,BTCEMC,BTCFCT,BTCMAID,BTCEGC,BTCSLS,BTCRADS,BTCDCR,BTCSAFEX,BTCBSD,BTCXVG,BTCPIVX,BTCXVC,BTCMEME,BTCSTEEM,BTC2GIVE,BTCLSK,BTCPDC,BTCBRK,BTCDGD,ETHDGD,BTCWAVES,BTCRISE,BTCLBC,BTCSBD,BTCBRX,BTCDRACO,BTCETC,ETHETC,BTCSTRAT,BTCUNB,BTCSYNX,BTCTRIG,BTCEBST,BTCVRM,BTCSEQ,BTCXAUR,BTCSNGLS,BTCREP,BTCSHIFT,BTCARDR,BTCXZC,BTCNEO,BTCZEC,BTCZCL,BTCIOP,BTCDAR,BTCGOLOS,BTCHKG,BTCUBQ,BTCKMD,BTCGBG,BTCSIB,BTCION,BTCLMC,BTCQWARK,BTCCRW,BTCSWT,BTCTIME,BTCMLN,BTCARK,BTCDYN,BTCTKS,BTCMUSIC,BTCDTB,BTCINCNT,BTCGBYTE,BTCGNT,BTCNXC,BTCEDG,BTCLGD,BTCTRST,ETHGNT,ETHREP,USDTETH,ETHWINGS,BTCWINGS,BTCRLC,BTCGNO,BTCGUP,BTCLUN,ETHGUP,ETHRLC,ETHLUN,ETHSNGLS,ETHGNO,BTCAPX,BTCTKN,ETHTKN,BTCHMQ,ETHHMQ,BTCANT,ETHTRST,ETHANT,BTCSC,ETHBAT,BTCBAT,BTCZEN,BTC1ST,BTCQRL,ETH1ST,ETHQRL,BTCCRB,ETHCRB,ETHLGD,BTCPTOY,ETHPTOY,BTCMYST,ETHMYST,BTCCFI,ETHCFI,BTCBNT,ETHBNT,BTCNMR,ETHNMR,ETHTIME,ETHLTC,ETHXRP,BTCSNT,ETHSNT,BTCDCT,BTCXEL,BTCMCO,ETHMCO,BTCADT,ETHADT,BTCFUN,ETHFUN,BTCPAY,ETHPAY,BTCMTL,ETHMTL,BTCSTORJ,ETHSTORJ,BTCADX,ETHADX,ETHDASH,ETHSC,ETHZEC,USDTZEC,USDTLTC,USDTETC,USDTXRP,BTCOMG,ETHOMG,BTCCVC,ETHCVC,BTCPART,BTCQTUM,ETHQTUM,ETHXMR,ETHXEM,ETHXLM,ETHNEO,USDTXMR,USDTDASH,ETHBCC,USDTBCC,BTCBCC,USDTNEO,ETHWAVES,ETHSTRAT,ETHDGB,ETHFCT,ETHBTS", - "EnabledPairs": "BTCLTC,BTCDOGE,BTCDASH", - "BaseCurrencies": "USD" + "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-NAUT,BTC-VRC,BTC-CURE,BTC-XBB,BTC-XMR,BTC-CLOAK,BTC-START,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-BTCD,BTC-VIA,BTC-UNO,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BLITZ,BTC-BAY,BTC-BTS,BTC-FAIR,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-SNRG,BTC-PKB,BTC-CPC,BTC-AEON,BTC-ETH,BTC-GCR,BTC-TX,BTC-BCY,BTC-EXP,BTC-INFX,BTC-OMNI,BTC-AMP,BTC-AGRS,BTC-XLM,BTC-BTA,USDT-BTC,BITCNY-BTC,BTC-CLUB,BTC-VOX,BTC-EMC,BTC-FCT,BTC-MAID,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-SAFEX,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-XVC,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-PDC,BTC-BRK,BTC-DGD,ETH-DGD,BTC-WAVES,BTC-RISE,BTC-LBC,BTC-SBD,BTC-BRX,BTC-DRACO,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-TRIG,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-XAUR,BTC-SNGLS,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-DAR,BTC-GOLOS,BTC-HKG,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-TIME,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-SNGLS,ETH-GNO,BTC-APX,BTC-TKN,ETH-TKN,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,ETH-1ST,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-MYST,ETH-MYST,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-TIME,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-FUN,ETH-FUN,BTC-PAY,ETH-PAY,BTC-MTL,ETH-MTL,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,ETH-BTS", + "EnabledPairs": "USDT-BTC", + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + } }, { "Name": "BTCC", @@ -114,10 +143,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCCNY,LTCCNY,LTCBTC", "EnabledPairs": "BTCCNY,LTCCNY,LTCBTC", - "BaseCurrencies": "CNY" + "BaseCurrencies": "CNY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false + } }, { "Name": "BTCE", @@ -128,10 +162,17 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", "EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", - "BaseCurrencies": "USD,RUR,EUR" + "BaseCurrencies": "USD,RUR,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_", + "Separator": "-" + } }, { "Name": "BTC Markets", @@ -142,10 +183,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", - "AvailablePairs": "LTC,BTC", - "EnabledPairs": "LTC,BTC", - "BaseCurrencies": "AUD" + "AvailablePairs": "LTCAUD,BTCAUD", + "EnabledPairs": "LTCAUD,BTCAUD", + "BaseCurrencies": "AUD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "COINUT", @@ -159,7 +205,13 @@ "ClientID": "ClientID", "AvailablePairs": "LTCBTC,ETCBTC,ETHBTC", "EnabledPairs": "LTCBTC,ETCBTC,ETHBTC", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "GDAX", @@ -171,9 +223,16 @@ "APIKey": "Key", "APISecret": "Secret", "ClientID": "ClientID", - "AvailablePairs": "BTCGBP,BTCEUR,ETHUSD,ETHBTC,LTCUSD,LTCBTC,BTCUSD", + "AvailablePairs": "LTCEUR,LTCBTC,BTCGBP,BTCEUR,ETHEUR,ETHBTC,LTCUSD,BTCUSD,ETHUSD", "EnabledPairs": "BTCUSD,BTCGBP,BTCEUR", - "BaseCurrencies": "USD,GBP,EUR" + "BaseCurrencies": "USD,GBP,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + } }, { "Name": "Gemini", @@ -184,10 +243,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCUSD,ETHBTC,ETHUSD", "EnabledPairs": "BTCUSD", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Huobi", @@ -198,10 +262,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", - "BaseCurrencies": "CNY" + "BaseCurrencies": "CNY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false + } }, { "Name": "ITBIT", @@ -215,7 +284,13 @@ "ClientID": "ClientID", "AvailablePairs": "XBTUSD,XBTSGD,XBTEUR", "EnabledPairs": "XBTUSD,XBTSGD,XBTEUR", - "BaseCurrencies": "USD,SGD,EUR" + "BaseCurrencies": "USD,SGD,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Kraken", @@ -226,10 +301,16 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", - "AvailablePairs": "ETCUSD,ICNETH,REPXBT,ZECXBT,ETHXBT,ETHXBT.d,ETHGBP,LTCXBT,XBTGBP.d,XDGXBT,XMRUSD,ZECUSD,ETCETH,ETHJPY,XBTCAD.d,XBTJPY.d,XBTUSD.d,XLMXBT,XLMEUR,XLMUSD,XMREUR,ETCXBT,ETHCAD.d,ETHEUR.d,ETHJPY.d,XBTEUR.d,ETHEUR,ETHGBP.d,ICNXBT,LTCEUR,REPEUR,XBTGBP,XBTJPY,ETHUSD,ETHUSD.d,LTCUSD,REPETH,XBTUSD,XMRXBT,ETCEUR,ETHCAD,REPUSD,XBTCAD,XBTEUR,XRPXBT,ZECEUR", + "AvailablePairs": "XBTUSD,ZECEUR,REPETH,XBTJPY,ETHEUR.D,LTCXBT,GNOXBT,ETHCAD.D,ETHEUR,ETHUSD,ICNXBT,XDGXBT,BCHUSD,DASHEUR,ETHJPY.D,MLNXBT,XBTCAD,GNOETH,ETCUSD,REPXBT,ETCXBT,ICNETH,ETHXBT,XBTJPY.D,XMREUR,XRPUSD,ZECXBT,ETHCAD,XBTGBP.D,MLNETH,BCHEUR,ETCEUR,XBTEUR,XLMXBT,XRPXBT,ETCETH,REPEUR,XMRUSD,ZECUSD,USDTUSD,ETHXBT.D,ETHJPY,ETHUSD.D,XBTUSD.D,LTCUSD,XBTCAD.D,BCHXBT,DASHUSD,EOSXBT,ETHGBP.D,XMRXBT,XRPEUR,DASHXBT,EOSETH,LTCEUR,XBTEUR.D", "EnabledPairs": "ETCUSD,XBTUSD,ETHUSD", - "BaseCurrencies": "EUR,USD,CAD,GBP,JPY" + "BaseCurrencies": "EUR,USD,CAD,GBP,JPY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Separator": "," + } }, { "Name": "LakeBTC", @@ -240,10 +321,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCUSD,BTCEUR,USDHKD,AUDUSD,BTCGBP,BTCNZD,USDJPY,BTCSGD,BTCNGN,EURUSD,USDSGD,NZDUSD,USDNGN,USDCHF,BTCJPY,BTCAUD,BTCCAD,BTCCHF,GBPUSD,USDCAD", "EnabledPairs": "BTCUSD,BTCAUD", - "BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD" + "BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Liqui", @@ -254,10 +340,18 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", - "AvailablePairs": "TIME_BTC,ETH_BTC,GNT_BTC,WAVES_BTC,ICN_BTC,1ST_BTC,WINGS_BTC,MLN_BTC,ROUND_BTC,VSL_BTC,LTC_BTC,DCT_BTC,INCNT_BTC,PLU_BTC,DASH_BTC", + "AvailablePairs": "SAN_BTC,OAX_BTC,VSL_BTC,PLU_USDT,GUP_ETH,SNT_ETH,EOS_ETH,ICN_USDT,CVC_USDT,DASH_BTC,DASH_USDT,WINGS_USDT,LUN_ETH,CFI_USDT,OAX_USDT,BCAP_ETH,MCO_BTC,STORJ_BTC,ICN_BTC,LTC_ETH,TAAS_BTC,BNT_ETH,QTUM_ETH,REP_ETH,RLC_BTC,HMQ_ETH,TIME_ETH,QRL_USDT,PTOY_USDT,LTC_BTC,GNT_BTC,RLC_USDT,SNT_BTC,RLC_ETH,TRST_USDT,MCO_ETH,ADX_BTC,VSL_USDT,TRST_ETH,DGD_USDT,BCC_ETH,SNM_ETH,DNT_ETH,GNT_ETH,TAAS_USDT,HMQ_USDT,BAT_ETH,STORJ_ETH,ADX_ETH,OMG_USDT,TIME_BTC,PLU_ETH,WINGS_ETH,SNGLS_BTC,CFI_ETH,SAN_ETH,DNT_USDT,STX_ETH,WAVES_BTC,1ST_ETH,INCNT_ETH,MYST_USDT,PTOY_ETH,MLN_USDT,QRL_BTC,ADX_USDT,PAY_ETH,STX_BTC,QTUM_BTC,CVC_ETH,STX_USDT,MLN_BTC,ICN_ETH,BTC_USDT,TRST_BTC,SNM_BTC,NET_BTC,CVC_BTC,OAX_ETH,1ST_BTC,GNT_USDT,GUP_BTC,BAT_USDT,BNT_USDT,STORJ_USDT,PLU_BTC,DASH_ETH,BCAP_USDT,QRL_ETH,PTOY_BTC,PAY_BTC,ZRX_ETH,ZRX_USDT,LTC_USDT,GNO_BTC,TKN_BTC,HMQ_BTC,MCO_USDT,GUP_USDT,BCC_BTC,XID_BTC,ETH_USDT,INCNT_USDT,GNO_USDT,CFI_BTC,WAVES_USDT,QTUM_USDT,NET_USDT,DNT_BTC,ROUND_ETH,REP_BTC,TKN_USDT,XID_USDT,DGD_ETH,MYST_ETH,SNT_USDT,PAY_USDT,BCC_USDT,ROUND_BTC,ANT_ETH,OMG_ETH,NET_ETH,DGD_BTC,SAN_USDT,WINGS_BTC,VSL_ETH,ROUND_USDT,LUN_BTC,LUN_USDT,EDG_USDT,ANT_USDT,EOS_USDT,ETH_BTC,INCNT_BTC,WAVES_ETH,TIME_USDT,EDG_BTC,XID_ETH,SNGLS_USDT,SNM_USDT,OMG_BTC,GNO_ETH,MGO_ETH,MGO_USDT,MYST_BTC,ZRX_BTC,BNT_BTC,MGO_BTC,SNGLS_ETH,1ST_USDT,EDG_ETH,REP_USDT,BCAP_BTC,ANT_BTC,MLN_ETH,TAAS_ETH,TKN_ETH,BAT_BTC,EOS_BTC", "EnabledPairs": "ETH_BTC,LTC_BTC,DASH_BTC", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_", + "Separator": "-" + } }, { "Name": "LocalBitcoins", @@ -268,10 +362,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", "EnabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", - "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR" + "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "OKCOIN China", @@ -282,10 +381,16 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", - "BaseCurrencies": "CNY" + "BaseCurrencies": "CNY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_" + } }, { "Name": "OKCOIN International", @@ -296,10 +401,16 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTCUSD,LTCUSD", "EnabledPairs": "BTCUSD,LTCUSD", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_" + } }, { "Name": "Poloniex", @@ -310,10 +421,17 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "ClientID": "", "AvailablePairs": "BTC_XUSD,BTC_FCT,BTC_MMNXT,BTC_NMC,BTC_BITUSD,BTC_RDD,BTC_XMR,BTC_XST,BTC_DSH,BTC_MAID,BTC_DGB,BTC_NEOS,BTC_BLK,BTC_NAUT,BTC_NBT,BTC_XCP,BTC_STR,BTC_BTCD,BTC_GRC,BTC_HUC,BTC_BBR,BTC_XDN,BTC_INDEX,BTC_IOC,BTC_SWARM,BTC_EMC2,BTC_MCN,BTC_NOXT,BTC_MINT,BTC_PTS,BTC_SC,BTC_GEO,BTC_XRP,BTC_FLO,BTC_BITS,BTC_HYP,BTC_XCR,BTC_LTBC,BTC_SYS,BTC_GMC,BTC_ETH,BTC_SYNC,BTC_GAP,BTC_BCN,BTC_C2,BTC_PINK,BTC_FIBRE,BTC_POT,BTC_QTL,BTC_SDC,BTC_XC,BTC_DASH,BTC_SILK,BTC_CLAM,BTC_NAV,BTC_PIGGY,BTC_BCY,BTC_MIL,BTC_XCN,BTC_YACC,BTC_BTS,BTC_QBK,BTC_SJCX,BTC_LQD,BTC_BURST,BTC_RIC,BTC_VRC,BTC_LTC,BTC_XPB,BTC_GRS,BTC_XCH,BTC_ARCH,BTC_QORA,BTC_HZ,BTC_NSR,BTC_XPM,BTC_BITCNY,BTC_EXE,BTC_XMG,BTC_BTC,BTC_BTM,BTC_NOBL,BTC_NXT,BTC_DOGE,BTC_CURE,BTC_MNTA,BTC_ADN,BTC_EXP,BTC_VTC,BTC_FLDC,BTC_MRS,BTC_MYR,BTC_OMNI,BTC_VNL,BTC_USDT,BTC_NOTE,BTC_WDC,BTC_BELA,BTC_VIA,BTC_CGA,BTC_DIEM,BTC_IFC,BTC_XDP,BTC_BLOCK,BTC_MMC,BTC_1CR,BTC_UNITY,BTC_XBC,BTC_GEMZ,BTC_FLT,BTC_PPC,BTC_XEM,BTC_RBY,BTC_CNMT,BTC_ABY,XMR_XDN,XMR_IFC,XMR_DIEM,XMR_BBR,XMR_DSH,XMR_BCN,XMR_LTC,XMR_MAID,XMR_DASH,XMR_BTCD,XMR_HYP,XMR_BLK,XMR_QORA,XMR_MNTA,XMR_NXT,USDT_BTC,USDT_ETH,USDT_XRP,USDT_DASH,USDT_LTC,USDT_NXT,USDT_XMR,USDT_STR", "EnabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + } } ] -} +} \ No newline at end of file diff --git a/currency/pair/pair.go b/currency/pair/pair.go index 3e0c8b3e..3b105ba0 100644 --- a/currency/pair/pair.go +++ b/currency/pair/pair.go @@ -1,6 +1,8 @@ package pair -import "strings" +import ( + "strings" +) // CurrencyItem is an exported string with methods to manipulate the data instead // of using array/slice access modifiers @@ -43,7 +45,7 @@ func (c CurrencyPair) Pair() CurrencyItem { return c.FirstCurrency + CurrencyItem(c.Delimiter) + c.SecondCurrency } -// Display formats and returns the currency based on user preferences, +// Display formats and returns the currency based on user preferences, // overriding the default Pair() display func (c CurrencyPair) Display(delimiter string, uppercase bool) CurrencyItem { var pair CurrencyItem @@ -79,6 +81,16 @@ func NewCurrencyPair(firstCurrency, secondCurrency string) CurrencyPair { } } +// NewCurrencyPairFromIndex returns a CurrencyPair via a currency string and +// specific index +func NewCurrencyPairFromIndex(currency, index string) CurrencyPair { + i := strings.Index(currency, index) + if i == 0 { + return NewCurrencyPair(currency[0:len(index)], currency[len(index):]) + } + return NewCurrencyPair(currency[0:i], currency[i:]) +} + // NewCurrencyPairFromString converts currency string into a new CurrencyPair // with or without delimeter func NewCurrencyPairFromString(currency string) CurrencyPair { diff --git a/currency/pair/pair_test.go b/currency/pair/pair_test.go index 91abf22a..e787bba2 100644 --- a/currency/pair/pair_test.go +++ b/currency/pair/pair_test.go @@ -140,6 +140,26 @@ func TestNewCurrencyPairDelimiter(t *testing.T) { } } +// NewCurrencyPairFromIndex returns a CurrencyPair via a currency string and +// specific index +func TestNewCurrencyPairFromIndex(t *testing.T) { + t.Parallel() + currency := "BTCUSD" + index := "BTC" + + pair := NewCurrencyPairFromIndex(currency, index) + pair.Delimiter = "-" + actual := pair.Pair() + + expected := CurrencyItem("BTC-USD") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } +} + func TestNewCurrencyPairFromString(t *testing.T) { t.Parallel() pairStr := "BTC-USD" diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 92f25b89..9688fbab 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -39,6 +39,12 @@ func (a *ANX) SetDefaults() { a.Verbose = false a.Websocket = false a.RESTPollingDelay = 10 + a.RequestCurrencyPairFormat.Delimiter = "" + a.RequestCurrencyPairFormat.Uppercase = true + a.RequestCurrencyPairFormat.Index = "BTC" + a.ConfigCurrencyPairFormat.Delimiter = "" + a.ConfigCurrencyPairFormat.Uppercase = true + a.ConfigCurrencyPairFormat.Index = "BTC" } //Setup is run on startup to setup exchange with config values @@ -55,6 +61,10 @@ func (a *ANX) Setup(exch config.ExchangeConfig) { a.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") a.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") a.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := a.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/anx/anx_types.go b/exchanges/anx/anx_types.go index 49b6956f..784fe4e9 100644 --- a/exchanges/anx/anx_types.go +++ b/exchanges/anx/anx_types.go @@ -30,11 +30,10 @@ type ANXOrderResponse struct { } type ANXTickerComponent struct { - Currency string `json:"currency"` - Display string `json:"display"` - DisplayShort string `json:"display_short"` - Value float64 `json:"value,string"` - ValueInt int64 `json:"value_int,string"` + Currency string `json:"currency"` + Display string `json:"display"` + DisplayShort string `json:"display_short"` + Value string `json:"value"` } type ANXTicker struct { diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 6584a5ac..23e8795a 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -2,6 +2,7 @@ package anx import ( "log" + "strconv" "time" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -22,8 +23,9 @@ func (a *ANX) Run() { } for a.Enabled { - for _, x := range a.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := a.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := a.GetTickerPrice(currency) if err != nil { @@ -51,12 +53,60 @@ func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { } tickerPrice.Pair = p - tickerPrice.Ask = tick.Data.Buy.Value - tickerPrice.Bid = tick.Data.Sell.Value - tickerPrice.Low = tick.Data.Low.Value - tickerPrice.Last = tick.Data.Last.Value - tickerPrice.Volume = tick.Data.Vol.Value - tickerPrice.High = tick.Data.High.Value + + if tick.Data.Sell.Value != "" { + tickerPrice.Ask, err = strconv.ParseFloat(tick.Data.Sell.Value, 64) + if err != nil { + return tickerPrice, err + } + } else { + tickerPrice.Ask = 0 + } + + if tick.Data.Buy.Value != "" { + tickerPrice.Bid, err = strconv.ParseFloat(tick.Data.Buy.Value, 64) + if err != nil { + return tickerPrice, err + } + } else { + tickerPrice.Bid = 0 + } + + if tick.Data.Low.Value != "" { + tickerPrice.Low, err = strconv.ParseFloat(tick.Data.Low.Value, 64) + if err != nil { + return tickerPrice, err + } + } else { + tickerPrice.Low = 0 + } + + if tick.Data.Last.Value != "" { + tickerPrice.Last, err = strconv.ParseFloat(tick.Data.Last.Value, 64) + if err != nil { + return tickerPrice, err + } + } else { + tickerPrice.Last = 0 + } + + if tick.Data.Vol.Value != "" { + tickerPrice.Volume, err = strconv.ParseFloat(tick.Data.Vol.Value, 64) + if err != nil { + return tickerPrice, err + } + } else { + tickerPrice.Volume = 0 + } + + if tick.Data.High.Value != "" { + tickerPrice.High, err = strconv.ParseFloat(tick.Data.High.Value, 64) + if err != nil { + return tickerPrice, err + } + } else { + tickerPrice.High = 0 + } ticker.ProcessTicker(a.GetName(), p, tickerPrice) return tickerPrice, nil } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index f18e8bb5..f1e14800 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -81,6 +81,10 @@ func (b *Bitfinex) SetDefaults() { b.Websocket = false b.RESTPollingDelay = 10 b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo) + b.RequestCurrencyPairFormat.Delimiter = "" + b.RequestCurrencyPairFormat.Uppercase = true + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Uppercase = true } // Setup takes in the supplied exchange configuration details and sets params @@ -97,6 +101,10 @@ func (b *Bitfinex) Setup(exch config.ExchangeConfig) { b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 65730c16..95268ce7 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -33,15 +33,16 @@ func (b *Bitfinex) Run() { if err != nil { log.Printf("%s Failed to get available symbols.\n", b.GetName()) } else { - err = b.UpdateAvailableCurrencies(exchangeProducts) + err = b.UpdateAvailableCurrencies(exchangeProducts, false) if err != nil { log.Printf("%s Failed to get config.\n", b.GetName()) } } for b.Enabled { - for _, x := range b.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := b.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := b.GetTickerPrice(currency) if err != nil { diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 8c4c3688..0cf02579 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -55,6 +55,10 @@ func (b *Bitstamp) SetDefaults() { b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 + b.RequestCurrencyPairFormat.Delimiter = "" + b.RequestCurrencyPairFormat.Uppercase = true + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Uppercase = true } func (b *Bitstamp) Setup(exch config.ExchangeConfig) { @@ -70,6 +74,10 @@ func (b *Bitstamp) Setup(exch config.ExchangeConfig) { b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 76710f91..bae10f95 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -28,8 +28,9 @@ func (b *Bitstamp) Run() { } for b.Enabled { - for _, x := range b.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := b.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := b.GetTickerPrice(currency) if err != nil { diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index cdcb857a..b5ffd7a9 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -66,6 +66,10 @@ func (b *Bittrex) SetDefaults() { b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 + b.RequestCurrencyPairFormat.Delimiter = "-" + b.RequestCurrencyPairFormat.Uppercase = true + b.ConfigCurrencyPairFormat.Delimiter = "-" + b.ConfigCurrencyPairFormat.Uppercase = true } // Setup method sets current configuration details if enabled @@ -82,6 +86,10 @@ func (b *Bittrex) Setup(exch config.ExchangeConfig) { b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 7689a478..645ddf30 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -28,24 +28,37 @@ func (b *Bittrex) Run() { if err != nil { log.Printf("%s Failed to get available symbols.\n", b.GetName()) } else { + forceUpgrade := false + if !common.DataContains(b.EnabledPairs, "-") || !common.DataContains(b.AvailablePairs, "-") { + forceUpgrade = true + } var currencies []string for x := range exchangeProducts { if !exchangeProducts[x].IsActive { continue } - currencies = append(currencies, - common.ReplaceString(exchangeProducts[x].MarketName, "-", "", -1)) + currencies = append(currencies, exchangeProducts[x].MarketName) } - err = b.UpdateAvailableCurrencies(currencies) + + if forceUpgrade { + enabledPairs := []string{"USDT-BTC"} + log.Println("WARNING: Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") + + err = b.UpdateEnabledCurrencies(enabledPairs, true) + if err != nil { + log.Printf("%s Failed to get config.\n", b.GetName()) + } + } + err = b.UpdateAvailableCurrencies(currencies, forceUpgrade) if err != nil { log.Printf("%s Failed to get config.\n", b.GetName()) } } for b.Enabled { - for _, x := range b.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) - currency.Delimiter = "-" + pairs := b.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := b.GetTickerPrice(currency) if err != nil { @@ -87,7 +100,7 @@ func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error } var tickerPrice ticker.TickerPrice - tick, err := b.GetMarketSummary(p.Pair().Lower().String()) + tick, err := b.GetMarketSummary(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return tickerPrice, err } @@ -108,7 +121,7 @@ func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, } var orderBook orderbook.OrderbookBase - orderbookNew, err := b.GetOrderbook(p.Pair().Lower().String()) + orderbookNew, err := b.GetOrderbook(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return orderBook, err } diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index 802f114c..86afe3cd 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -53,6 +53,10 @@ func (b *BTCC) SetDefaults() { b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 + b.RequestCurrencyPairFormat.Delimiter = "" + b.RequestCurrencyPairFormat.Uppercase = false + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Uppercase = true } //Setup is run on startup to setup exchange with config values @@ -69,6 +73,10 @@ func (b *BTCC) Setup(exch config.ExchangeConfig) { b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index bccf0296..45a24c90 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -28,8 +28,9 @@ func (b *BTCC) Run() { } for b.Enabled { - for _, x := range b.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := b.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := b.GetTickerPrice(currency) if err != nil { @@ -51,7 +52,7 @@ func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { } var tickerPrice ticker.TickerPrice - tick, err := b.GetTicker(p.Pair().Lower().String()) + tick, err := b.GetTicker(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return tickerPrice, err } @@ -74,7 +75,7 @@ func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err } var orderBook orderbook.OrderbookBase - orderbookNew, err := b.GetOrderBook(p.Pair().Lower().String(), 100) + orderbookNew, err := b.GetOrderBook(exchange.FormatExchangeCurrency(b.GetName(), p).String(), 100) if err != nil { return orderBook, err } diff --git a/exchanges/btce/btce.go b/exchanges/btce/btce.go index f96819c3..bebd3674 100644 --- a/exchanges/btce/btce.go +++ b/exchanges/btce/btce.go @@ -48,6 +48,11 @@ func (b *BTCE) SetDefaults() { b.Websocket = false b.RESTPollingDelay = 10 b.Ticker = make(map[string]BTCeTicker) + b.RequestCurrencyPairFormat.Delimiter = "_" + b.RequestCurrencyPairFormat.Uppercase = false + b.RequestCurrencyPairFormat.Separator = "-" + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Uppercase = true } func (b *BTCE) Setup(exch config.ExchangeConfig) { @@ -63,7 +68,10 @@ func (b *BTCE) Setup(exch config.ExchangeConfig) { b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") - + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index c3d6bed9..786fe23c 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -24,16 +24,17 @@ func (b *BTCE) Run() { log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) } - pairs := []string{} - for _, x := range b.EnabledPairs { - x = common.StringToLower(x[0:3] + "_" + x[3:6]) - pairs = append(pairs, x) + pairs := b.GetEnabledCurrencies() + pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(b.Name, pairs) + if err != nil { + log.Println(err) + b.Enabled = false + return } - pairsString := common.JoinStrings(pairs, "-") for b.Enabled { go func() { - ticker, err := b.GetTicker(pairsString) + ticker, err := b.GetTicker(pairsCollated.String()) if err != nil { log.Println(err) return @@ -51,9 +52,9 @@ func (b *BTCE) Run() { func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice - tick, ok := b.Ticker[p.Pair().Lower().String()] + tick, ok := b.Ticker[exchange.FormatExchangeCurrency(b.Name, p).String()] if !ok { - return tickerPrice, errors.New("Unable to get currency.") + return tickerPrice, errors.New("unable to get currency") } tickerPrice.Pair = p tickerPrice.Ask = tick.Buy @@ -73,7 +74,7 @@ func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err } var orderBook orderbook.OrderbookBase - orderbookNew, err := b.GetDepth(p.Pair().Lower().String()) + orderbookNew, err := b.GetDepth(exchange.FormatExchangeCurrency(b.Name, p).String()) if err != nil { return orderBook, err } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 6f7fe3fb..0760585b 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -38,6 +38,10 @@ func (b *BTCMarkets) SetDefaults() { b.Websocket = false b.RESTPollingDelay = 10 b.Ticker = make(map[string]BTCMarketsTicker) + b.RequestCurrencyPairFormat.Delimiter = "" + b.RequestCurrencyPairFormat.Uppercase = true + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Uppercase = true } func (b *BTCMarkets) Setup(exch config.ExchangeConfig) { @@ -53,7 +57,10 @@ func (b *BTCMarkets) Setup(exch config.ExchangeConfig) { b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") - + err := b.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 2ba3ed2f..ea480f0c 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -4,6 +4,8 @@ import ( "log" "time" + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" @@ -22,9 +24,36 @@ func (b *BTCMarkets) Run() { log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) } + if !common.DataContains(b.EnabledPairs, "AUD") || !common.DataContains(b.EnabledPairs, "AUD") { + enabledPairs := []string{} + for x := range b.EnabledPairs { + enabledPairs = append(enabledPairs, b.EnabledPairs[x]+"AUD") + } + + availablePairs := []string{} + for x := range b.AvailablePairs { + availablePairs = append(availablePairs, b.AvailablePairs[x]+"AUD") + } + + log.Println("BTCMarkets: Upgrading available and enabled pairs") + + err := b.UpdateEnabledCurrencies(enabledPairs, true) + if err != nil { + log.Printf("%s Failed to get config.\n", b.GetName()) + return + } + + err = b.UpdateAvailableCurrencies(availablePairs, true) + if err != nil { + log.Printf("%s Failed to get config.\n", b.GetName()) + return + } + } + for b.Enabled { - for _, x := range b.EnabledPairs { - curr := pair.NewCurrencyPair(x, "AUD") + pairs := b.GetEnabledCurrencies() + for x := range pairs { + curr := pairs[x] go func() { ticker, err := b.GetTickerPrice(curr) if err != nil { diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 5d262881..a26d966c 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -48,6 +48,10 @@ func (c *COINUT) SetDefaults() { c.Verbose = false c.Websocket = false c.RESTPollingDelay = 10 + c.RequestCurrencyPairFormat.Delimiter = "" + c.RequestCurrencyPairFormat.Uppercase = true + c.ConfigCurrencyPairFormat.Delimiter = "" + c.ConfigCurrencyPairFormat.Uppercase = true } func (c *COINUT) Setup(exch config.ExchangeConfig) { @@ -63,6 +67,10 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) { c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := c.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 66dcd242..96c65c86 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -40,14 +40,15 @@ func (c *COINUT) Run() { currencies = append(currencies, x) } - err = c.UpdateAvailableCurrencies(currencies) + err = c.UpdateAvailableCurrencies(currencies, false) if err != nil { log.Printf("%s Failed to get config.\n", c.GetName()) } for c.Enabled { - for _, x := range c.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := c.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := c.GetTickerPrice(currency) if err != nil { @@ -62,8 +63,8 @@ func (c *COINUT) Run() { } } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the COINUT exchange -func (e *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the COINUT exchange +func (c *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo /* response.ExchangeName = e.GetName() diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 3eb08590..fc13269d 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -51,6 +51,8 @@ type Base struct { EnabledPairs []string WebsocketURL string APIUrl string + RequestCurrencyPairFormat config.CurrencyPairFormatConfig + ConfigCurrencyPairFormat config.CurrencyPairFormatConfig } // IBotExchange enforces standard functions for all exchanges supported in @@ -63,11 +65,51 @@ type IBotExchange interface { IsEnabled() bool GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) - GetEnabledCurrencies() []string + GetEnabledCurrencies() []pair.CurrencyPair GetExchangeAccountInfo() (AccountInfo, error) GetAuthenticatedAPISupport() bool } +// SetCurrencyPairFormat checks the exchange request and config currency pair +// formats and sets it to a default setting if it doesn't exist +func (e *Base) SetCurrencyPairFormat() error { + cfg := config.GetConfig() + exch, err := cfg.GetExchangeConfig(e.Name) + if err != nil { + return err + } + + update := false + if exch.RequestCurrencyPairFormat == nil { + exch.RequestCurrencyPairFormat = &config.CurrencyPairFormatConfig{ + Delimiter: e.RequestCurrencyPairFormat.Delimiter, + Uppercase: e.RequestCurrencyPairFormat.Uppercase, + Separator: e.RequestCurrencyPairFormat.Separator, + Index: e.RequestCurrencyPairFormat.Index, + } + update = true + } else { + e.RequestCurrencyPairFormat = *exch.RequestCurrencyPairFormat + } + + if exch.ConfigCurrencyPairFormat == nil { + exch.ConfigCurrencyPairFormat = &config.CurrencyPairFormatConfig{ + Delimiter: e.ConfigCurrencyPairFormat.Delimiter, + Uppercase: e.ConfigCurrencyPairFormat.Uppercase, + Separator: e.ConfigCurrencyPairFormat.Separator, + Index: e.ConfigCurrencyPairFormat.Index, + } + update = true + } else { + e.ConfigCurrencyPairFormat = *exch.ConfigCurrencyPairFormat + } + + if update { + return cfg.UpdateExchangeConfig(exch) + } + return nil +} + // GetAuthenticatedAPISupport returns whether the exchange supports // authenticated API requests func (e *Base) GetAuthenticatedAPISupport() bool { @@ -81,14 +123,135 @@ func (e *Base) GetName() string { // GetEnabledCurrencies is a method that returns the enabled currency pairs of // the exchange base -func (e *Base) GetEnabledCurrencies() []string { - return e.EnabledPairs +func (e *Base) GetEnabledCurrencies() []pair.CurrencyPair { + var pairs []pair.CurrencyPair + for x := range e.EnabledPairs { + var currencyPair pair.CurrencyPair + if e.RequestCurrencyPairFormat.Delimiter != "" { + if e.ConfigCurrencyPairFormat.Delimiter != "" { + if e.ConfigCurrencyPairFormat.Delimiter == e.RequestCurrencyPairFormat.Delimiter { + currencyPair = pair.NewCurrencyPairDelimiter(e.EnabledPairs[x], + e.RequestCurrencyPairFormat.Delimiter) + } else { + currencyPair = pair.NewCurrencyPairDelimiter(e.EnabledPairs[x], + e.ConfigCurrencyPairFormat.Delimiter) + currencyPair.Delimiter = "-" + } + } else { + if e.ConfigCurrencyPairFormat.Index != "" { + currencyPair = pair.NewCurrencyPairFromIndex(e.EnabledPairs[x], + e.ConfigCurrencyPairFormat.Index) + } else { + currencyPair = pair.NewCurrencyPair(e.EnabledPairs[x][0:3], + e.EnabledPairs[x][3:]) + } + } + } else { + if e.ConfigCurrencyPairFormat.Delimiter != "" { + currencyPair = pair.NewCurrencyPairDelimiter(e.EnabledPairs[x], + e.ConfigCurrencyPairFormat.Delimiter) + } else { + if e.ConfigCurrencyPairFormat.Index != "" { + currencyPair = pair.NewCurrencyPairFromIndex(e.EnabledPairs[x], + e.ConfigCurrencyPairFormat.Index) + } else { + currencyPair = pair.NewCurrencyPair(e.EnabledPairs[x][0:3], + e.EnabledPairs[x][3:]) + } + } + } + pairs = append(pairs, currencyPair) + } + return pairs } // GetAvailableCurrencies is a method that returns the available currency pairs // of the exchange base -func (e *Base) GetAvailableCurrencies() []string { - return e.AvailablePairs +func (e *Base) GetAvailableCurrencies() []pair.CurrencyPair { + var pairs []pair.CurrencyPair + for x := range e.AvailablePairs { + var currencyPair pair.CurrencyPair + if e.RequestCurrencyPairFormat.Delimiter != "" { + if e.ConfigCurrencyPairFormat.Delimiter != "" { + if e.ConfigCurrencyPairFormat.Delimiter == e.RequestCurrencyPairFormat.Delimiter { + currencyPair = pair.NewCurrencyPairDelimiter(e.AvailablePairs[x], + e.RequestCurrencyPairFormat.Delimiter) + } else { + currencyPair = pair.NewCurrencyPairDelimiter(e.AvailablePairs[x], + e.ConfigCurrencyPairFormat.Delimiter) + currencyPair.Delimiter = "-" + } + } else { + if e.ConfigCurrencyPairFormat.Index != "" { + currencyPair = pair.NewCurrencyPairFromIndex(e.AvailablePairs[x], + e.ConfigCurrencyPairFormat.Index) + } else { + currencyPair = pair.NewCurrencyPair(e.AvailablePairs[x][0:3], + e.AvailablePairs[x][3:]) + } + } + } else { + if e.ConfigCurrencyPairFormat.Delimiter != "" { + currencyPair = pair.NewCurrencyPairDelimiter(e.AvailablePairs[x], + e.ConfigCurrencyPairFormat.Delimiter) + } else { + if e.ConfigCurrencyPairFormat.Index != "" { + currencyPair = pair.NewCurrencyPairFromIndex(e.AvailablePairs[x], + e.ConfigCurrencyPairFormat.Index) + } else { + currencyPair = pair.NewCurrencyPair(e.AvailablePairs[x][0:3], + e.AvailablePairs[x][3:]) + } + } + } + pairs = append(pairs, currencyPair) + } + return pairs +} + +// GetExchangeFormatCurrencySeperator returns whether or not a specific +// exchange contains a separator used for API requests +func GetExchangeFormatCurrencySeperator(exchName string) bool { + cfg := config.GetConfig() + exch, err := cfg.GetExchangeConfig(exchName) + if err != nil { + return false + } + + if exch.RequestCurrencyPairFormat.Separator != "" { + return true + } + return false +} + +// GetAndFormatExchangeCurrencies returns a pair.CurrencyItem string containing +// the exchanges formatted currency pairs +func GetAndFormatExchangeCurrencies(exchName string, pairs []pair.CurrencyPair) (pair.CurrencyItem, error) { + var currencyItems pair.CurrencyItem + cfg := config.GetConfig() + exch, err := cfg.GetExchangeConfig(exchName) + if err != nil { + return currencyItems, err + } + + for x := range pairs { + currencyItems += FormatExchangeCurrency(exchName, pairs[x]) + if x == len(pairs)-1 { + continue + } + currencyItems += pair.CurrencyItem(exch.RequestCurrencyPairFormat.Separator) + } + return currencyItems, nil +} + +// FormatExchangeCurrency is a method that formats and returns a currency pair +// based on the user currency display preferences +func FormatExchangeCurrency(exchName string, p pair.CurrencyPair) pair.CurrencyItem { + cfg := config.GetConfig() + exch, _ := cfg.GetExchangeConfig(exchName) + + return p.Display(exch.RequestCurrencyPairFormat.Delimiter, + exch.RequestCurrencyPairFormat.Uppercase) } // FormatCurrency is a method that formats and returns a currency pair @@ -126,20 +289,50 @@ func (e *Base) SetAPIKeys(APIKey, APISecret, ClientID string, b64Decode bool) { } } -// UpdateAvailableCurrencies is a method that sets new pairs to the current -// exchange -func (e *Base) UpdateAvailableCurrencies(exchangeProducts []string) error { +// UpdateEnabledCurrencies is a method that sets new pairs to the current +// exchange. Setting force to true upgrades the enabled currencies +func (e *Base) UpdateEnabledCurrencies(exchangeProducts []string, force bool) error { exchangeProducts = common.SplitStrings(common.StringToUpper(common.JoinStrings(exchangeProducts, ",")), ",") - diff := common.StringSliceDifference(e.AvailablePairs, exchangeProducts) - if len(diff) > 0 { + diff := common.StringSliceDifference(e.EnabledPairs, exchangeProducts) + if force || len(diff) > 0 { cfg := config.GetConfig() exch, err := cfg.GetExchangeConfig(e.Name) if err != nil { return err } - log.Printf("%s Updating available pairs. Difference: %s.\n", e.Name, diff) - exch.AvailablePairs = common.JoinStrings(exchangeProducts, ",") - cfg.UpdateExchangeConfig(exch) + + if force { + log.Printf("%s forced update of enabled pairs.", e.Name) + } else { + log.Printf("%s Updating available pairs. Difference: %s.\n", e.Name, diff) + } + exch.EnabledPairs = common.JoinStrings(exchangeProducts, ",") + e.EnabledPairs = exchangeProducts + return cfg.UpdateExchangeConfig(exch) + } + return nil +} + +// UpdateAvailableCurrencies is a method that sets new pairs to the current +// exchange. Setting force to true upgrades the available currencies +func (e *Base) UpdateAvailableCurrencies(exchangeProducts []string, force bool) error { + exchangeProducts = common.SplitStrings(common.StringToUpper(common.JoinStrings(exchangeProducts, ",")), ",") + diff := common.StringSliceDifference(e.AvailablePairs, exchangeProducts) + if force || len(diff) > 0 { + cfg := config.GetConfig() + exch, err := cfg.GetExchangeConfig(e.Name) + if err != nil { + return err + } + + if force { + log.Printf("%s forced update of available pairs.", e.Name) + } else { + log.Printf("%s Updating available pairs. Difference: %s.\n", e.Name, diff) + } + exch.AvailablePairs = common.JoinStrings(exchangeProducts, ",") + e.AvailablePairs = exchangeProducts + return cfg.UpdateExchangeConfig(exch) } return nil } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index bc17f371..a3bff955 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -3,9 +3,8 @@ package exchange import ( "testing" - "github.com/thrasher-/gocryptotrader/currency/pair" - "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency/pair" ) func TestGetName(t *testing.T) { @@ -19,6 +18,29 @@ func TestGetName(t *testing.T) { } } +func TestSetCurrencyPairFormat(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Failed to load config file. Error: %s", err) + } + + exch, err := cfg.GetExchangeConfig("GDAX") + if err != nil { + t.Fatalf("Failed to load GDAX exchange config. Error: %s", err) + } + + exch.RequestCurrencyPairFormat = nil + exch.ConfigCurrencyPairFormat = nil + + err = cfg.UpdateExchangeConfig(exch) + if err != nil { + t.Fatalf("Failed to update GDAX config. Error: %s", err) + } + + // to-do +} + func TestGetEnabledCurrencies(t *testing.T) { enabledPairs := []string{"BTCUSD", "BTCAUD", "LTCUSD", "LTCAUD"} GetEnabledCurrencies := Base{ @@ -27,7 +49,7 @@ func TestGetEnabledCurrencies(t *testing.T) { } enCurr := GetEnabledCurrencies.GetEnabledCurrencies() - if enCurr[0] != "BTCUSD" { + if enCurr[0].Pair().String() != "BTCUSD" { t.Error("Test Failed - Exchange GetEnabledCurrencies() incorrect string") } } @@ -40,11 +62,75 @@ func TestGetAvailableCurrencies(t *testing.T) { } enCurr := GetEnabledCurrencies.GetAvailableCurrencies() - if enCurr[0] != "BTCUSD" { + if enCurr[0].Pair().String() != "BTCUSD" { t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") } } +func TestGetExchangeFormatCurrencySeperator(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Failed to load config file. Error: %s", err) + } + + expected := true + actual := GetExchangeFormatCurrencySeperator("BTCE") + + if expected != actual { + t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v", + expected, actual) + } + + expected = false + actual = GetExchangeFormatCurrencySeperator("LocalBitcoins") + + if expected != actual { + t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v", + expected, actual) + } +} + +func TestGetAndFormatExchangeCurrencies(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Failed to load config file. Error: %s", err) + } + + var pairs []pair.CurrencyPair + pairs = append(pairs, pair.NewCurrencyPairDelimiter("BTC_USD", "_")) + pairs = append(pairs, pair.NewCurrencyPairDelimiter("LTC_BTC", "_")) + + actual, err := GetAndFormatExchangeCurrencies("Liqui", pairs) + if err != nil { + t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies error %s", err) + } + expected := pair.CurrencyItem("btc_usd-ltc_btc") + + if actual.String() != expected.String() { + t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies %s != %s", + actual, expected) + } +} + +func TestFormatExchangeCurrency(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Failed to load config file. Error: %s", err) + } + + pair := pair.NewCurrencyPair("BTC", "USD") + expected := "BTC-USD" + actual := FormatExchangeCurrency("GDAX", pair) + + if actual.String() != expected { + t.Errorf("Test failed - Exchange TestFormatExchangeCurrency %s != %s", + actual, expected) + } +} + func TestFormatCurrency(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) @@ -97,6 +183,23 @@ func TestSetAPIKeys(t *testing.T) { SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", true) } +func TestUpdateEnabledCurrencies(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + UAC := Base{Name: "ANX"} + enabledCurrencies := []string{"ltc", "btc", "usd", "aud"} + + if err != nil { + t.Error( + "Test Failed - Exchange UpdateEnabledCurrencies() did not set correct values", + ) + } + err2 := UAC.UpdateEnabledCurrencies(enabledCurrencies, false) + if err2 != nil { + t.Errorf("Test Failed - Exchange UpdateEnabledCurrencies() error: %s", err2) + } +} + func TestUpdateAvailableCurrencies(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) @@ -108,7 +211,7 @@ func TestUpdateAvailableCurrencies(t *testing.T) { "Test Failed - Exchange UpdateAvailableCurrencies() did not set correct values", ) } - err2 := UAC.UpdateAvailableCurrencies(exchangeProducts) + err2 := UAC.UpdateAvailableCurrencies(exchangeProducts, false) if err2 != nil { t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err2) } diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index 7c73f68d..04a2d361 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -46,6 +46,10 @@ func (g *GDAX) SetDefaults() { g.Verbose = false g.Websocket = false g.RESTPollingDelay = 10 + g.RequestCurrencyPairFormat.Delimiter = "-" + g.RequestCurrencyPairFormat.Uppercase = true + g.ConfigCurrencyPairFormat.Delimiter = "" + g.ConfigCurrencyPairFormat.Uppercase = true } func (g *GDAX) Setup(exch config.ExchangeConfig) { @@ -61,6 +65,10 @@ func (g *GDAX) Setup(exch config.ExchangeConfig) { g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := g.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 88d41e43..6c519990 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -37,16 +37,16 @@ func (g *GDAX) Run() { currencies = append(currencies, x.ID[0:3]+x.ID[4:]) } } - err = g.UpdateAvailableCurrencies(currencies) + err = g.UpdateAvailableCurrencies(currencies, false) if err != nil { log.Printf("%s Failed to get config.\n", g.GetName()) } } for g.Enabled { - for _, x := range g.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) - currency.Delimiter = "-" + pairs := g.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := g.GetTickerPrice(currency) @@ -88,12 +88,12 @@ func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { } var tickerPrice ticker.TickerPrice - tick, err := g.GetTicker(p.Pair().String()) + tick, err := g.GetTicker(exchange.FormatExchangeCurrency(g.Name, p).String()) if err != nil { return ticker.TickerPrice{}, err } - stats, err := g.GetStats(p.Pair().String()) + stats, err := g.GetStats(exchange.FormatExchangeCurrency(g.Name, p).String()) if err != nil { return ticker.TickerPrice{}, err diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 654347da..638ebd6c 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -45,6 +45,10 @@ func (g *Gemini) SetDefaults() { g.Verbose = false g.Websocket = false g.RESTPollingDelay = 10 + g.RequestCurrencyPairFormat.Delimiter = "" + g.RequestCurrencyPairFormat.Uppercase = true + g.ConfigCurrencyPairFormat.Delimiter = "" + g.ConfigCurrencyPairFormat.Uppercase = true } func (g *Gemini) Setup(exch config.ExchangeConfig) { @@ -60,6 +64,10 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := g.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 272f5eb9..4e266d66 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -26,15 +26,16 @@ func (g *Gemini) Run() { if err != nil { log.Printf("%s Failed to get available symbols.\n", g.GetName()) } else { - err = g.UpdateAvailableCurrencies(exchangeProducts) + err = g.UpdateAvailableCurrencies(exchangeProducts, false) if err != nil { log.Printf("%s Failed to get config.\n", g.GetName()) } } for g.Enabled { - for _, x := range g.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := g.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := g.GetTickerPrice(currency) if err != nil { diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f3edab71..a4d65a93 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -29,6 +29,10 @@ func (h *HUOBI) SetDefaults() { h.Verbose = false h.Websocket = false h.RESTPollingDelay = 10 + h.RequestCurrencyPairFormat.Delimiter = "" + h.RequestCurrencyPairFormat.Uppercase = false + h.ConfigCurrencyPairFormat.Delimiter = "" + h.ConfigCurrencyPairFormat.Uppercase = true } func (h *HUOBI) Setup(exch config.ExchangeConfig) { @@ -44,6 +48,10 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) { h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := h.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index a17c28d2..9324b984 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -29,8 +29,9 @@ func (h *HUOBI) Run() { } for h.Enabled { - for _, x := range h.EnabledPairs { - curr := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := h.GetEnabledCurrencies() + for x := range pairs { + curr := pairs[x] go func() { ticker, err := h.GetTickerPrice(curr) if err != nil { diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 47a97532..ae2f1b94 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -31,6 +31,10 @@ func (i *ItBit) SetDefaults() { i.Verbose = false i.Websocket = false i.RESTPollingDelay = 10 + i.RequestCurrencyPairFormat.Delimiter = "" + i.RequestCurrencyPairFormat.Uppercase = true + i.ConfigCurrencyPairFormat.Delimiter = "" + i.ConfigCurrencyPairFormat.Uppercase = true } func (i *ItBit) Setup(exch config.ExchangeConfig) { @@ -46,6 +50,10 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) { i.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") i.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") i.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := i.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index deb80fa2..d444c024 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -22,8 +22,9 @@ func (i *ItBit) Run() { } for i.Enabled { - for _, x := range i.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := i.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := i.GetTickerPrice(currency) if err != nil { diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 0be7fe3c..ee99b2b4 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -55,6 +55,11 @@ func (k *Kraken) SetDefaults() { k.Websocket = false k.RESTPollingDelay = 10 k.Ticker = make(map[string]KrakenTicker) + k.RequestCurrencyPairFormat.Delimiter = "" + k.RequestCurrencyPairFormat.Uppercase = true + k.RequestCurrencyPairFormat.Separator = "," + k.ConfigCurrencyPairFormat.Delimiter = "" + k.ConfigCurrencyPairFormat.Uppercase = true } func (k *Kraken) Setup(exch config.ExchangeConfig) { @@ -70,6 +75,10 @@ func (k *Kraken) Setup(exch config.ExchangeConfig) { k.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") k.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") k.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := k.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 7c5626bc..356f7380 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -4,7 +4,6 @@ import ( "log" "time" - "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" @@ -30,21 +29,28 @@ func (k *Kraken) Run() { for _, v := range assetPairs { exchangeProducts = append(exchangeProducts, v.Altname) } - err = k.UpdateAvailableCurrencies(exchangeProducts) + err = k.UpdateAvailableCurrencies(exchangeProducts, false) if err != nil { log.Printf("%s Failed to get config.\n", k.GetName()) } } for k.Enabled { - err := k.GetTicker(common.JoinStrings(k.EnabledPairs, ",")) + pairs := k.GetEnabledCurrencies() + pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs) + if err != nil { + log.Println(err) + continue + } + err = k.GetTicker(pairsCollated.String()) if err != nil { log.Println(err) } else { - for _, x := range k.EnabledPairs { - ticker := k.Ticker[x] - log.Printf("Kraken %s Last %f High %f Low %f Volume %f\n", x, ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(k.GetName(), x[0:3], x[3:], ticker.Last, ticker.Volume) + for _, x := range pairs { + ticker := k.Ticker[x.Pair().String()] + log.Printf("Kraken %s Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(x).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + stats.AddExchangeInfo(k.GetName(), x.GetFirstCurrency().String(), x.GetSecondCurrency().String(), + ticker.Last, ticker.Volume) } } time.Sleep(time.Second * k.RESTPollingDelay) diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 97121ecb..79c0a9c9 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -42,6 +42,10 @@ func (l *LakeBTC) SetDefaults() { l.Verbose = false l.Websocket = false l.RESTPollingDelay = 10 + l.RequestCurrencyPairFormat.Delimiter = "" + l.RequestCurrencyPairFormat.Uppercase = true + l.ConfigCurrencyPairFormat.Delimiter = "" + l.ConfigCurrencyPairFormat.Uppercase = true } func (l *LakeBTC) Setup(exch config.ExchangeConfig) { @@ -57,6 +61,10 @@ func (l *LakeBTC) Setup(exch config.ExchangeConfig) { l.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") l.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") l.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := l.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index a2a1f920..8bf35b45 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -23,8 +23,9 @@ func (l *LakeBTC) Run() { } for l.Enabled { - for _, x := range l.EnabledPairs { - currency := pair.NewCurrencyPair(x[0:3], x[3:]) + pairs := l.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] ticker, err := l.GetTickerPrice(currency) if err != nil { log.Println(err) diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 15ecdcd2..9c0578e5 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -46,6 +46,11 @@ func (l *Liqui) SetDefaults() { l.Websocket = false l.RESTPollingDelay = 10 l.Ticker = make(map[string]LiquiTicker) + l.RequestCurrencyPairFormat.Delimiter = "_" + l.RequestCurrencyPairFormat.Uppercase = false + l.RequestCurrencyPairFormat.Separator = "-" + l.ConfigCurrencyPairFormat.Delimiter = "_" + l.ConfigCurrencyPairFormat.Uppercase = true } func (l *Liqui) Setup(exch config.ExchangeConfig) { @@ -61,6 +66,10 @@ func (l *Liqui) Setup(exch config.ExchangeConfig) { l.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") l.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") l.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := l.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 634fc1f4..6ad4a709 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -29,23 +29,23 @@ func (l *Liqui) Run() { log.Printf("%s Unable to fetch info.\n", l.GetName()) } else { exchangeProducts := l.GetAvailablePairs(true) - err = l.UpdateAvailableCurrencies(exchangeProducts) + err = l.UpdateAvailableCurrencies(exchangeProducts, false) if err != nil { log.Printf("%s Failed to get config.\n", l.GetName()) } } - pairs := []string{} - for _, x := range l.EnabledPairs { - currencies := common.SplitStrings(x, "_") - x = common.StringToLower(currencies[0]) + "_" + common.StringToLower(currencies[1]) - pairs = append(pairs, x) + pairsString, err := exchange.GetAndFormatExchangeCurrencies(l.Name, + l.GetEnabledCurrencies()) + if err != nil { + log.Println(err) + l.Enabled = false + return } - pairsString := common.JoinStrings(pairs, "-") for l.Enabled { go func() { - ticker, err := l.GetTicker(pairsString) + ticker, err := l.GetTicker(pairsString.String()) if err != nil { log.Println(err) return @@ -63,9 +63,9 @@ func (l *Liqui) Run() { func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice - tick, ok := l.Ticker[p.Pair().Lower().String()] + tick, ok := l.Ticker[exchange.FormatExchangeCurrency(l.Name, p).String()] if !ok { - return tickerPrice, errors.New("Unable to get currency.") + return tickerPrice, errors.New("unable to get currency") } tickerPrice.Pair = p tickerPrice.Ask = tick.Buy @@ -85,7 +85,7 @@ func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er } var orderBook orderbook.OrderbookBase - orderbookNew, err := l.GetDepth(p.Pair().Lower().String()) + orderbookNew, err := l.GetDepth(exchange.FormatExchangeCurrency(l.Name, p).String()) if err != nil { return orderBook, err } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index d657869e..5370503e 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -38,6 +38,10 @@ func (l *LocalBitcoins) SetDefaults() { l.Verbose = false l.Websocket = false l.RESTPollingDelay = 10 + l.RequestCurrencyPairFormat.Delimiter = "" + l.RequestCurrencyPairFormat.Uppercase = true + l.ConfigCurrencyPairFormat.Delimiter = "" + l.ConfigCurrencyPairFormat.Uppercase = true } func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) { @@ -53,6 +57,10 @@ func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) { l.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") l.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") l.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := l.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index a4642ba2..9d891da1 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -22,8 +22,9 @@ func (l *LocalBitcoins) Run() { } for l.Enabled { - for _, x := range l.EnabledPairs { - currency := pair.NewCurrencyPair("BTC", x[3:]) + pairs := l.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] ticker, err := l.GetTickerPrice(currency) if err != nil { diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index 0f2994aa..47ed673e 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -78,6 +78,13 @@ type OKCoin struct { WebsocketConn *websocket.Conn } +func (o *OKCoin) setCurrencyPairFormats() { + o.RequestCurrencyPairFormat.Delimiter = "_" + o.RequestCurrencyPairFormat.Uppercase = false + o.ConfigCurrencyPairFormat.Delimiter = "" + o.ConfigCurrencyPairFormat.Uppercase = true +} + func (o *OKCoin) SetDefaults() { o.SetErrorDefaults() o.SetWebsocketErrorDefaults() @@ -92,10 +99,12 @@ func (o *OKCoin) SetDefaults() { o.Name = "OKCOIN International" o.WebsocketURL = OKCOIN_WEBSOCKET_URL okcoinDefaultsSet = true + o.setCurrencyPairFormats() } else { o.APIUrl = OKCOIN_API_URL_CHINA o.Name = "OKCOIN China" o.WebsocketURL = OKCOIN_WEBSOCKET_URL_CHINA + o.setCurrencyPairFormats() } } @@ -112,6 +121,10 @@ func (o *OKCoin) Setup(exch config.ExchangeConfig) { o.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") o.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") o.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := o.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 7e869fa6..6677276f 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -29,14 +29,14 @@ func (o *OKCoin) Run() { } for o.Enabled { - for _, x := range o.EnabledPairs { - curr := pair.NewCurrencyPair(x[0:3], x[3:]) - curr.Delimiter = "_" + pairs := o.GetEnabledCurrencies() + for x := range pairs { + curr := pairs[x] if o.APIUrl == OKCOIN_API_URL { for _, y := range o.FuturesValues { futuresValue := y go func() { - ticker, err := o.GetFuturesTicker(curr.Pair().Lower().String(), futuresValue) + ticker, err := o.GetFuturesTicker(exchange.FormatExchangeCurrency(o.Name, curr).String(), futuresValue) if err != nil { log.Println(err) return @@ -81,7 +81,7 @@ func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, } var tickerPrice ticker.TickerPrice - tick, err := o.GetTicker(currency.Pair().Lower().String()) + tick, err := o.GetTicker(exchange.FormatExchangeCurrency(o.Name, currency).String()) if err != nil { return tickerPrice, err } @@ -103,7 +103,7 @@ func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.Orderbook } var orderBook orderbook.OrderbookBase - orderbookNew, err := o.GetOrderBook(currency.Pair().Lower().String(), 200, false) + orderbookNew, err := o.GetOrderBook(exchange.FormatExchangeCurrency(o.Name, currency).String(), 200, false) if err != nil { return orderBook, err } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 0ce8e3b0..e34cde58 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "log" "net/url" "strconv" "time" @@ -57,6 +58,10 @@ func (p *Poloniex) SetDefaults() { p.Verbose = false p.Websocket = false p.RESTPollingDelay = 10 + p.RequestCurrencyPairFormat.Delimiter = "_" + p.RequestCurrencyPairFormat.Uppercase = true + p.ConfigCurrencyPairFormat.Delimiter = "_" + p.ConfigCurrencyPairFormat.Uppercase = true } func (p *Poloniex) Setup(exch config.ExchangeConfig) { @@ -72,6 +77,10 @@ func (p *Poloniex) Setup(exch config.ExchangeConfig) { p.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") p.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") p.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := p.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 1c4220f3..bccc8597 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -28,8 +28,9 @@ func (p *Poloniex) Run() { } for p.Enabled { - for _, x := range p.EnabledPairs { - currency := pair.NewCurrencyPairDelimiter(x, "_") + pairs := p.GetEnabledCurrencies() + for x := range pairs { + currency := pairs[x] go func() { ticker, err := p.GetTickerPrice(currency) if err != nil { diff --git a/testdata/configtest.dat b/testdata/configtest.dat index 9757e8c9..731df5bc 100644 --- a/testdata/configtest.dat +++ b/testdata/configtest.dat @@ -11,26 +11,26 @@ { "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", "CoinType": "BTC", - "Balance": 124178.0002442, - "Decscription": "" + "Balance": 124178.00647714, + "Description": "" }, { "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", "CoinType": "BTC", - "Balance": 103439.83659727, - "Decscription": "" + "Balance": 107843.84030984, + "Description": "" }, { "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", "CoinType": "LTC", - "Balance": 3000000.05, - "Decscription": "" + "Balance": 100000.052, + "Description": "" }, { "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", "CoinType": "ETH", - "Balance": 5774999.820458524, - "Decscription": "" + "Balance": 3.224999915984445e+24, + "Description": "" } ] }, @@ -64,7 +64,15 @@ "APISecret": "Secret", "AvailablePairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", "EnabledPairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", - "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD" + "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Index": "BTC" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Index": "BTC" + } }, { "Name": "Bitfinex", @@ -75,9 +83,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,BFXUSD,BFXBTC,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC", + "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BCCBTC,BCUBTC,BCCUSD,BCUUSD,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH", "EnabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Bitstamp", @@ -91,7 +105,13 @@ "ClientID": "ClientID", "AvailablePairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "EnabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", - "BaseCurrencies": "USD,EUR" + "BaseCurrencies": "USD,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "BTCC", @@ -104,7 +124,13 @@ "APISecret": "Secret", "AvailablePairs": "BTCCNY,LTCCNY,LTCBTC", "EnabledPairs": "BTCCNY,LTCCNY,LTCBTC", - "BaseCurrencies": "CNY" + "BaseCurrencies": "CNY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false + } }, { "Name": "BTCE", @@ -117,7 +143,15 @@ "APISecret": "Secret", "AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", "EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", - "BaseCurrencies": "USD,RUR,EUR" + "BaseCurrencies": "USD,RUR,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_", + "Separator": "-" + } }, { "Name": "BTC Markets", @@ -128,9 +162,15 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "LTC,BTC", - "EnabledPairs": "LTC,BTC", - "BaseCurrencies": "AUD" + "AvailablePairs": "LTCAUD,BTCAUD", + "EnabledPairs": "LTCAUD,BTCAUD", + "BaseCurrencies": "AUD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "GDAX", @@ -142,9 +182,16 @@ "APIKey": "Key", "APISecret": "Secret", "ClientID": "ClientID", - "AvailablePairs": "BTCGBP,BTCEUR,ETHUSD,ETHBTC,LTCUSD,LTCBTC,BTCUSD", + "AvailablePairs": "LTCEUR,LTCBTC,BTCGBP,BTCEUR,ETHEUR,ETHBTC,LTCUSD,BTCUSD,ETHUSD", "EnabledPairs": "BTCUSD,BTCGBP,BTCEUR", - "BaseCurrencies": "USD,GBP,EUR" + "BaseCurrencies": "USD,GBP,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + } }, { "Name": "Gemini", @@ -157,7 +204,13 @@ "APISecret": "Secret", "AvailablePairs": "BTCUSD,ETHBTC,ETHUSD", "EnabledPairs": "BTCUSD", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Huobi", @@ -170,7 +223,13 @@ "APISecret": "Secret", "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", - "BaseCurrencies": "CNY" + "BaseCurrencies": "CNY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false + } }, { "Name": "ITBIT", @@ -184,7 +243,13 @@ "ClientID": "ClientID", "AvailablePairs": "XBTUSD,XBTSGD,XBTEUR", "EnabledPairs": "XBTUSD,XBTSGD,XBTEUR", - "BaseCurrencies": "USD,SGD,EUR" + "BaseCurrencies": "USD,SGD,EUR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Kraken", @@ -195,9 +260,16 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "ETCUSD,ICNETH,REPXBT,ZECXBT,ETHXBT,ETHXBT.d,ETHGBP,LTCXBT,XBTGBP.d,XDGXBT,XMRUSD,ZECUSD,ETCETH,ETHJPY,XBTCAD.d,XBTJPY.d,XBTUSD.d,XLMXBT,XLMEUR,XLMUSD,XMREUR,ETCXBT,ETHCAD.d,ETHEUR.d,ETHJPY.d,XBTEUR.d,ETHEUR,ETHGBP.d,ICNXBT,LTCEUR,REPEUR,XBTGBP,XBTJPY,ETHUSD,ETHUSD.d,LTCUSD,REPETH,XBTUSD,XMRXBT,ETCEUR,ETHCAD,REPUSD,XBTCAD,XBTEUR,XRPXBT,ZECEUR", + "AvailablePairs": "REPETH,ZECXBT,ETCUSD,ETHCAD,ETCEUR,ETHXBT.D,XBTJPY.D,EOSXBT,USDTUSD,LTCEUR,XBTUSD,ETHUSD,XBTEUR.D,BCHEUR,GNOXBT,ICNXBT,XBTEUR,ZECUSD,ETCXBT,ICNETH,LTCXBT,XRPXBT,ZECEUR,DASHUSD,ETHEUR.D,ETHJPY.D,LTCUSD,XMRXBT,BCHXBT,ETHJPY,GNOETH,XDGXBT,ETHCAD.D,XRPUSD,ETHEUR,XMRUSD,MLNETH,REPEUR,XBTCAD.D,XRPEUR,BCHUSD,ETHXBT,XBTJPY,XBTUSD.D,XLMXBT,DASHXBT,XBTGBP.D,MLNXBT,REPXBT,XBTCAD,DASHEUR,ETHGBP.D,ETHUSD.D,XMREUR,EOSETH,ETCETH", "EnabledPairs": "ETCUSD,XBTUSD,ETHUSD", - "BaseCurrencies": "EUR,USD,CAD,GBP,JPY" + "BaseCurrencies": "EUR,USD,CAD,GBP,JPY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Separator": "," + } }, { "Name": "LakeBTC", @@ -210,7 +282,13 @@ "APISecret": "Secret", "AvailablePairs": "BTCUSD,BTCEUR,USDHKD,AUDUSD,BTCGBP,BTCNZD,USDJPY,BTCSGD,BTCNGN,EURUSD,USDSGD,NZDUSD,USDNGN,USDCHF,BTCJPY,BTCAUD,BTCCAD,BTCCHF,GBPUSD,USDCAD", "EnabledPairs": "BTCUSD,BTCAUD", - "BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD" + "BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "Liqui", @@ -221,9 +299,18 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "TIME_BTC,ETH_BTC,GNT_BTC,WAVES_BTC,ICN_BTC,1ST_BTC,WINGS_BTC,MLN_BTC,ROUND_BTC,VSL_BTC,LTC_BTC,DCT_BTC,INCNT_BTC,PLU_BTC,DASH_BTC", + "AvailablePairs": "XID_BTC,OAX_ETH,LTC_BTC,TIME_BTC,GNT_ETH,LUN_BTC,HMQ_ETH,EOS_USDT,NET_ETH,DASH_BTC,ETH_USDT,VSL_USDT,BAT_ETH,OMG_ETH,SAN_ETH,DGD_ETH,STX_ETH,LTC_USDT,TAAS_USDT,BAT_USDT,QRL_USDT,EOS_BTC,NET_USDT,GNT_BTC,ANT_BTC,ANT_USDT,SNGLS_ETH,CFI_ETH,CFI_USDT,PLU_ETH,BTC_USDT,ROUND_USDT,GNO_USDT,ZRX_USDT,ROUND_BTC,ICN_ETH,WAVES_USDT,OMG_BTC,QTUM_BTC,LTC_ETH,EDG_USDT,WINGS_ETH,TKN_BTC,CVC_USDT,REP_USDT,RLC_BTC,GNO_BTC,ADX_USDT,OMG_USDT,PLU_BTC,EDG_BTC,BNT_ETH,OAX_BTC,STX_BTC,BAT_BTC,BCC_USDT,VSL_ETH,REP_ETH,HMQ_BTC,SNT_ETH,ZRX_BTC,MLN_ETH,LUN_ETH,TKN_ETH,CFI_BTC,DGD_USDT,DNT_ETH,STORJ_ETH,TAAS_BTC,HMQ_USDT,BCAP_BTC,BNT_USDT,MYST_BTC,SNM_BTC,ADX_ETH,CVC_BTC,WAVES_BTC,TRST_ETH,PLU_USDT,GNT_USDT,REP_BTC,QRL_BTC,MGO_USDT,BCC_ETH,ZRX_ETH,ICN_BTC,MLN_BTC,RLC_USDT,TAAS_ETH,PAY_BTC,SAN_USDT,WINGS_BTC,1ST_ETH,LUN_USDT,QRL_ETH,SNGLS_USDT,GUP_BTC,PTOY_USDT,MCO_USDT,ICN_USDT,INCNT_USDT,STORJ_USDT,INCNT_BTC,DASH_ETH,GUP_ETH,SNT_BTC,SNT_USDT,ADX_BTC,DGD_BTC,BCC_BTC,VSL_BTC,WINGS_USDT,GUP_USDT,MYST_USDT,PAY_USDT,XID_USDT,MYST_ETH,SNGLS_BTC,SNM_ETH,CVC_ETH,PTOY_BTC,SNM_USDT,1ST_BTC,TIME_ETH,1ST_USDT,MLN_USDT,RLC_ETH,BCAP_ETH,XID_ETH,QTUM_ETH,WAVES_ETH,GNO_ETH,BCAP_USDT,SAN_BTC,TIME_USDT,STORJ_BTC,QTUM_USDT,ROUND_ETH,EDG_ETH,PTOY_ETH,TKN_USDT,BNT_BTC,MGO_BTC,PAY_ETH,INCNT_ETH,DASH_USDT,MCO_BTC,OAX_USDT,DNT_BTC,DNT_USDT,MCO_ETH,EOS_ETH,ETH_BTC,TRST_BTC,TRST_USDT,ANT_ETH,MGO_ETH,NET_BTC,STX_USDT", "EnabledPairs": "ETH_BTC,LTC_BTC,DASH_BTC", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_", + "Separator": "-" + } }, { "Name": "LocalBitcoins", @@ -236,7 +323,13 @@ "APISecret": "Secret", "AvailablePairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", "EnabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", - "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR" + "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": true + } }, { "Name": "OKCOIN China", @@ -249,7 +342,14 @@ "APISecret": "Secret", "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", - "BaseCurrencies": "CNY" + "BaseCurrencies": "CNY", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_" + } }, { "Name": "OKCOIN International", @@ -262,7 +362,14 @@ "APISecret": "Secret", "AvailablePairs": "BTCUSD,LTCUSD", "EnabledPairs": "BTCUSD,LTCUSD", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_" + } }, { "Name": "Poloniex", @@ -275,7 +382,15 @@ "APISecret": "Secret", "AvailablePairs": "BTC_XUSD,BTC_FCT,BTC_MMNXT,BTC_NMC,BTC_BITUSD,BTC_RDD,BTC_XMR,BTC_XST,BTC_DSH,BTC_MAID,BTC_DGB,BTC_NEOS,BTC_BLK,BTC_NAUT,BTC_NBT,BTC_XCP,BTC_STR,BTC_BTCD,BTC_GRC,BTC_HUC,BTC_BBR,BTC_XDN,BTC_INDEX,BTC_IOC,BTC_SWARM,BTC_EMC2,BTC_MCN,BTC_NOXT,BTC_MINT,BTC_PTS,BTC_SC,BTC_GEO,BTC_XRP,BTC_FLO,BTC_BITS,BTC_HYP,BTC_XCR,BTC_LTBC,BTC_SYS,BTC_GMC,BTC_ETH,BTC_SYNC,BTC_GAP,BTC_BCN,BTC_C2,BTC_PINK,BTC_FIBRE,BTC_POT,BTC_QTL,BTC_SDC,BTC_XC,BTC_DASH,BTC_SILK,BTC_CLAM,BTC_NAV,BTC_PIGGY,BTC_BCY,BTC_MIL,BTC_XCN,BTC_YACC,BTC_BTS,BTC_QBK,BTC_SJCX,BTC_LQD,BTC_BURST,BTC_RIC,BTC_VRC,BTC_LTC,BTC_XPB,BTC_GRS,BTC_XCH,BTC_ARCH,BTC_QORA,BTC_HZ,BTC_NSR,BTC_XPM,BTC_BITCNY,BTC_EXE,BTC_XMG,BTC_BTC,BTC_BTM,BTC_NOBL,BTC_NXT,BTC_DOGE,BTC_CURE,BTC_MNTA,BTC_ADN,BTC_EXP,BTC_VTC,BTC_FLDC,BTC_MRS,BTC_MYR,BTC_OMNI,BTC_VNL,BTC_USDT,BTC_NOTE,BTC_WDC,BTC_BELA,BTC_VIA,BTC_CGA,BTC_DIEM,BTC_IFC,BTC_XDP,BTC_BLOCK,BTC_MMC,BTC_1CR,BTC_UNITY,BTC_XBC,BTC_GEMZ,BTC_FLT,BTC_PPC,BTC_XEM,BTC_RBY,BTC_CNMT,BTC_ABY,XMR_XDN,XMR_IFC,XMR_DIEM,XMR_BBR,XMR_DSH,XMR_BCN,XMR_LTC,XMR_MAID,XMR_DASH,XMR_BTCD,XMR_HYP,XMR_BLK,XMR_QORA,XMR_MNTA,XMR_NXT,USDT_BTC,USDT_ETH,USDT_XRP,USDT_DASH,USDT_LTC,USDT_NXT,USDT_XMR,USDT_STR", "EnabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", - "BaseCurrencies": "USD" + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "_" + } } ] } \ No newline at end of file diff --git a/ticker_routes.go b/ticker_routes.go index 4ec16ea3..3d7e1ded 100644 --- a/ticker_routes.go +++ b/ticker_routes.go @@ -62,15 +62,13 @@ func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { "Getting enabled currencies for '" + individualBot.GetName() + "'", ) currencies := individualBot.GetEnabledCurrencies() - log.Println(currencies) - for _, currency := range currencies { - tickerPrice, err := individualBot.GetTickerPrice( - pair.NewCurrencyPairFromString(currency), - ) + for x := range currencies { + currency := currencies[x] + + tickerPrice, err := individualBot.GetTickerPrice(currency) if err != nil { continue } - log.Println(tickerPrice) individualExchange.ExchangeValues = append( individualExchange.ExchangeValues, tickerPrice, From 4dcce85810650f9e807bc1f037241f886e7e86da Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 24 Aug 2017 16:56:57 +1000 Subject: [PATCH 02/32] Default to HTTPS everywhere in project --- common/common_test.go | 12 ++++++------ currency/currency.go | 2 +- exchanges/huobi/huobi.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/common/common_test.go b/common/common_test.go index 806c9d85..e7328612 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -435,28 +435,28 @@ func TestSendHTTPRequest(t *testing.T) { headers["Content-Type"] = "application/x-www-form-urlencoded" _, err := SendHTTPRequest( - methodGarbage, "http://query.yahooapis.com/v1/public/yql", headers, + methodGarbage, "https://query.yahooapis.com/v1/public/yql", headers, strings.NewReader(""), ) if err == nil { t.Error("Test failed. ") } _, err = SendHTTPRequest( - methodPost, "http://query.yahooapis.com/v1/public/yql", headers, + methodPost, "https://query.yahooapis.com/v1/public/yql", headers, strings.NewReader(""), ) if err != nil { t.Errorf("Test failed. %s ", err) } _, err = SendHTTPRequest( - methodGet, "http://query.yahooapis.com/v1/public/yql", headers, + methodGet, "https://query.yahooapis.com/v1/public/yql", headers, strings.NewReader(""), ) if err != nil { t.Errorf("Test failed. %s ", err) } _, err = SendHTTPRequest( - methodDelete, "http://query.yahooapis.com/v1/public/yql", headers, + methodDelete, "https://query.yahooapis.com/v1/public/yql", headers, strings.NewReader(""), ) if err != nil { @@ -524,8 +524,8 @@ func TestJSONEncode(t *testing.T) { } func TestEncodeURLValues(t *testing.T) { - urlstring := "http://www.test.com" - expectedOutput := `http://www.test.com?env=TEST%2FDATABASE&format=json&q=SELECT+%2A+from+yahoo.finance.xchange+WHERE+pair+in+%28%22BTC%2CUSD%22%29` + urlstring := "https://www.test.com" + expectedOutput := `https://www.test.com?env=TEST%2FDATABASE&format=json&q=SELECT+%2A+from+yahoo.finance.xchange+WHERE+pair+in+%28%22BTC%2CUSD%22%29` values := url.Values{} values.Set("q", fmt.Sprintf( "SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", "BTC,USD"), diff --git a/currency/currency.go b/currency/currency.go index 794cea42..c6165c2f 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -41,7 +41,7 @@ type YahooJSONResponse struct { const ( maxCurrencyPairsPerRequest = 350 - yahooYQLURL = "http://query.yahooapis.com/v1/public/yql" + yahooYQLURL = "https://query.yahooapis.com/v1/public/yql?" yahooDatabase = "store://datatables.org/alltableswithkeys" // DefaultCurrencies has the default minimum of FIAT values DefaultCurrencies = "USD,AUD,EUR,CNY" diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index a4d65a93..5b15a233 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -61,7 +61,7 @@ func (h *HUOBI) GetFee() float64 { func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { resp := HuobiTickerResponse{} - path := fmt.Sprintf("http://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) + path := fmt.Sprintf("https://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) err := common.SendHTTPGetRequest(path, true, &resp) if err != nil { @@ -71,7 +71,7 @@ func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { } func (h *HUOBI) GetOrderBook(symbol string) (HuobiOrderbook, error) { - path := fmt.Sprintf("http://api.huobi.com/staticmarket/depth_%s_json.js", symbol) + path := fmt.Sprintf("https://api.huobi.com/staticmarket/depth_%s_json.js", symbol) resp := HuobiOrderbook{} err := common.SendHTTPGetRequest(path, true, &resp) if err != nil { From 40a4ff4fdba809780ad55ed9a66e496e83109733 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 25 Aug 2017 17:00:07 +1000 Subject: [PATCH 03/32] Only set authenticated API settings when authenticated API requests in config option is enabled --- exchanges/exchange.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exchanges/exchange.go b/exchanges/exchange.go index fc13269d..e89a56a7 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -274,6 +274,10 @@ func (e *Base) IsEnabled() bool { // SetAPIKeys is a method that sets the current API keys for the exchange func (e *Base) SetAPIKeys(APIKey, APISecret, ClientID string, b64Decode bool) { + if !e.AuthenticatedAPISupport { + return + } + e.APIKey = APIKey e.ClientID = ClientID From d1bdc6af5dba94b6baaef7a314ae9fb0e1e84f4f Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 28 Aug 2017 09:06:53 +1000 Subject: [PATCH 04/32] Use fixer.io currency rates API instead of Yahoo due to Yahoo service issues --- currency/currency.go | 97 +++++++++++++++++++++++++++++++++++---- currency/currency_test.go | 29 +++++++++--- 2 files changed, 111 insertions(+), 15 deletions(-) diff --git a/currency/currency.go b/currency/currency.go index c6165c2f..32cfe22d 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -39,10 +39,19 @@ type YahooJSONResponse struct { } } +// FixerResponse contains the data fields for the Fixer API response +type FixerResponse struct { + Base string `json:"base"` + Date string `json:"date"` + Rates map[string]float64 `json:"rates"` +} + const ( maxCurrencyPairsPerRequest = 350 yahooYQLURL = "https://query.yahooapis.com/v1/public/yql?" yahooDatabase = "store://datatables.org/alltableswithkeys" + yahooEnabled = false + fixerAPI = "http://api.fixer.io/latest" // DefaultCurrencies has the default minimum of FIAT values DefaultCurrencies = "USD,AUD,EUR,CNY" // DefaultCryptoCurrencies has the default minimum of crytpocurrency values @@ -53,6 +62,7 @@ const ( // queries var ( CurrencyStore map[string]Rate + CurrencyStoreFixer map[string]float64 BaseCurrencies string CryptoCurrencies string ErrCurrencyDataNotFetched = errors.New("yahoo currency data has not been fetched yet") @@ -170,11 +180,11 @@ func SeedCurrencyData(fiatCurrencies string) error { fiatCurrencies = DefaultCurrencies } - err := QueryYahooCurrencyValues(fiatCurrencies) - if err != nil { - return ErrQueryingYahoo + if yahooEnabled { + return QueryYahooCurrencyValues(fiatCurrencies) } - return nil + + return FetchFixerCurrencyData() } // MakecurrencyPairs takes all supported currency and turns them into pairs. @@ -196,21 +206,90 @@ func MakecurrencyPairs(supportedCurrencies string) string { // ConvertCurrency for example converts $1 USD to the equivalent Japanese Yen // or vice versa. func ConvertCurrency(amount float64, from, to string) (float64, error) { - currency := common.StringToUpper(from + to) + from = common.StringToUpper(from) + to = common.StringToUpper(to) - _, ok := CurrencyStore[currency] + if from == to { + return amount, nil + } + + if yahooEnabled { + currency := from + to + _, ok := CurrencyStore[currency] + if !ok { + err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):]) + if err != nil { + return 0, err + } + } + + result, ok := CurrencyStore[currency] + if !ok { + return 0, ErrCurrencyNotFound + } + return amount * result.Rate, nil + } + + _, ok := CurrencyStoreFixer[from] if !ok { - err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):]) + err := FetchFixerCurrencyData() if err != nil { return 0, err } } - result, ok := CurrencyStore[currency] + var resultFrom float64 + var resultTo float64 + + // First check if we're converting to USD, USD doesn't exist in the rates map + if to == "USD" { + resultFrom, ok = CurrencyStoreFixer[from] + if !ok { + return 0, ErrCurrencyNotFound + } + return amount / resultFrom, nil + } + + // Check to see if we're converting from USD + if from == "USD" { + resultTo, ok = CurrencyStoreFixer[to] + if !ok { + return 0, ErrCurrencyNotFound + } + return resultTo * amount, nil + } + + // Otherwise convert to USD, then to the target currency + resultFrom, ok = CurrencyStoreFixer[from] if !ok { return 0, ErrCurrencyNotFound } - return amount * result.Rate, nil + + converted := amount / resultFrom + resultTo, ok = CurrencyStoreFixer[to] + if !ok { + return 0, ErrCurrencyNotFound + } + + return converted * resultTo, nil +} + +// FetchFixerCurrencyData seeds the variable C +func FetchFixerCurrencyData() error { + var result FixerResponse + values := url.Values{} + values.Set("base", "USD") + url := common.EncodeURLValues(fixerAPI, values) + + CurrencyStoreFixer = make(map[string]float64) + + err := common.SendHTTPGetRequest(url, true, &result) + if err != nil { + return err + } + + CurrencyStoreFixer = result.Rates + return nil } // FetchYahooCurrencyData seeds the variable CurrencyStore; this is a diff --git a/currency/currency_test.go b/currency/currency_test.go index 2fa00516..17a081d1 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -274,12 +274,14 @@ func TestSeedCurrencyData(t *testing.T) { err2, currencyRequestUSDAUD, ) } - err3 := SeedCurrencyData(currencyRequestObtuse) - if err3 == nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err3, currencyRequestObtuse, - ) + if yahooEnabled { + err3 := SeedCurrencyData(currencyRequestObtuse) + if err3 == nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err3, currencyRequestObtuse, + ) + } } } @@ -323,7 +325,18 @@ func TestConvertCurrency(t *testing.T) { } } +func TestFetchFixerCurrencyData(t *testing.T) { + err := FetchFixerCurrencyData() + if err != nil { + t.Errorf("Test failed. FetchFixerCurrencyData returned %s", err) + } +} + func TestFetchYahooCurrencyData(t *testing.T) { + if !yahooEnabled { + return + } + t.Parallel() var fetchData []string fiatCurrencies := DefaultCurrencies @@ -344,6 +357,10 @@ func TestFetchYahooCurrencyData(t *testing.T) { } func TestQueryYahooCurrencyValues(t *testing.T) { + if !yahooEnabled { + return + } + err := QueryYahooCurrencyValues(DefaultCurrencies) if err != nil { t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err) From 120d59cec146c01e88d3df22da5d8dee2ce03837 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 28 Aug 2017 09:23:24 +1000 Subject: [PATCH 05/32] Fix TestSetAPIKeys test --- exchanges/exchange_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index a3bff955..37039d60 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -172,10 +172,17 @@ func TestIsEnabled(t *testing.T) { func TestSetAPIKeys(t *testing.T) { SetAPIKeys := Base{ - Name: "TESTNAME", - Enabled: false, + Name: "TESTNAME", + Enabled: false, + AuthenticatedAPISupport: false, } + SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", false) + if SetAPIKeys.APIKey != "" && SetAPIKeys.APISecret != "" && SetAPIKeys.ClientID != "" { + t.Error("Test Failed - SetAPIKeys() set values without authenticated API support enabled") + } + + SetAPIKeys.AuthenticatedAPISupport = true SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", false) if SetAPIKeys.APIKey != "RocketMan" && SetAPIKeys.APISecret != "Digereedoo" && SetAPIKeys.ClientID != "007" { t.Error("Test Failed - Exchange SetAPIKeys() did not set correct values") From 341302e91e6503913a2e86eece5bf413343b72d0 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 28 Aug 2017 13:06:00 +1000 Subject: [PATCH 06/32] Fix various tests after test branch merge --- config/config_test.go | 2 +- exchanges/anx/anx_test.go | 3 ++- exchanges/bitfinex/bitfinex_test.go | 28 +++++++++----------- exchanges/bitstamp/bitstamp_test.go | 30 +++++++++++++++------- exchanges/bittrex/bittrex_test.go | 32 +++++++++++++++-------- exchanges/btcc/btcc_test.go | 30 ++++++++++++++++------ exchanges/btcmarkets/btcmarkets_test.go | 34 ++++++++++++++++++------- testdata/configtest.dat | 21 +++++++++++++++ 8 files changed, 125 insertions(+), 55 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index f31c5abd..3ca3aeac 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,7 +5,7 @@ import ( ) func TestGetConfigEnabledExchanges(t *testing.T) { - defaultEnabledExchanges := 17 + defaultEnabledExchanges := 18 GetConfigEnabledExchanges := GetConfig() err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile) if err != nil { diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index 70ad4fd8..c5ede53e 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -35,6 +35,7 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { setup := ANX{} + setup.Name = "ANX" anxSetupConfig := config.GetConfig() anxSetupConfig.LoadConfig("../../testdata/configtest.dat") anxConfig, err := anxSetupConfig.GetExchangeConfig("ANX") @@ -49,7 +50,7 @@ func TestSetup(t *testing.T) { if setup.AuthenticatedAPISupport != false { t.Error("Test Failed - ANX Setup() incorrect values set") } - if len(setup.APIKey) <= 0 { + if len(setup.APIKey) != 0 { t.Error("Test Failed - ANX Setup() incorrect values set") } if len(setup.APISecret) != 0 { diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 5bffc8ec..03363c5d 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -8,7 +8,6 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/currency" ) // Please supply your own keys here to do better tests @@ -30,24 +29,21 @@ func TestSetDefaults(t *testing.T) { } func TestSetup(t *testing.T) { - testConfig := config.ExchangeConfig{ - Enabled: true, - AuthenticatedAPISupport: true, - APIKey: testAPIKey, - APISecret: testAPISecret, - RESTPollingDelay: time.Duration(10), - Verbose: false, - Websocket: true, - BaseCurrencies: currency.DefaultCurrencies, - AvailablePairs: currency.MakecurrencyPairs(currency.DefaultCurrencies), - EnabledPairs: currency.MakecurrencyPairs(currency.DefaultCurrencies), + setup := Bitfinex{} + setup.Name = "Bitfinex" + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") + if err != nil { + t.Error("Test Failed - Bitfinex Setup() init error") } + setup.Setup(bfxConfig) - b.Setup(testConfig) + b.SetDefaults() + b.Setup(bfxConfig) - if !b.Enabled || !b.AuthenticatedAPISupport || b.APIKey != testAPIKey || - b.APISecret != testAPISecret || b.RESTPollingDelay != time.Duration(10) || - b.Verbose || !b.Websocket || len(b.BaseCurrencies) < 1 || + if !b.Enabled || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) || + b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 || len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { t.Error("Test Failed - Bitfinex Setup values not set correctly") } diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index e7133de2..ce298211 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -3,6 +3,7 @@ package bitstamp import ( "net/url" "testing" + "time" "github.com/thrasher-/gocryptotrader/config" ) @@ -39,18 +40,29 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { t.Parallel() b := Bitstamp{} - conf := config.ExchangeConfig{ - Name: "bla", - Enabled: true, - AuthenticatedAPISupport: true, + b.Name = "Bitstamp" + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + bConfig, err := cfg.GetExchangeConfig("Bitstamp") + if err != nil { + t.Error("Test Failed - Bitstamp Setup() init error") } - b.Setup(conf) - if b.Name != "bla" && b.Enabled != true && b.AuthenticatedAPISupport != true { - t.Error("Test Failed - Setup() error") + b.SetDefaults() + b.Setup(bConfig) + + if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) || + b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 || + len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + t.Error("Test Failed - Bitstamp Setup values not set correctly") + } + + bConfig.Enabled = false + b.Setup(bConfig) + + if b.IsEnabled() { + t.Error("Test failed - Bitstamp TestSetup incorrect value") } - conf.Enabled = false - b.Setup(conf) } func TestGetFee(t *testing.T) { diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 32f7786f..99e5e209 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -2,6 +2,7 @@ package bittrex import ( "testing" + "time" "github.com/thrasher-/gocryptotrader/config" ) @@ -23,20 +24,29 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { t.Parallel() - exch := config.ExchangeConfig{ - Name: "Bittrex", - APIKey: apiKey, - } - exch.Enabled = true b := Bittrex{} - b.Setup(exch) - if b.APIKey != apiKey { - t.Error("Test Failed - Bittrex - Setup() error") + b.Name = "Bittrex" + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + bConfig, err := cfg.GetExchangeConfig("Bittrex") + if err != nil { + t.Error("Test Failed - Bittrex Setup() init error") } - exch.Enabled = false - b.Setup(exch) + + b.SetDefaults() + b.Setup(bConfig) + + if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) || + b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 || + len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + t.Error("Test Failed - Bittrex Setup values not set correctly") + } + + bConfig.Enabled = false + b.Setup(bConfig) + if b.IsEnabled() { - t.Error("Test Failed - Bittrex - Setup() error") + t.Error("Test failed - Bittrex TestSetup incorrect value") } } diff --git a/exchanges/btcc/btcc_test.go b/exchanges/btcc/btcc_test.go index aca0c326..6108fcc8 100644 --- a/exchanges/btcc/btcc_test.go +++ b/exchanges/btcc/btcc_test.go @@ -20,17 +20,31 @@ func TestSetDefaults(t *testing.T) { } func TestSetup(t *testing.T) { - conf := config.ExchangeConfig{ - Enabled: true, + t.Parallel() + b := BTCC{} + b.Name = "BTCC" + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + bConfig, err := cfg.GetExchangeConfig("BTCC") + if err != nil { + t.Error("Test Failed - BTCC Setup() init error") } - b.Setup(conf) - conf = config.ExchangeConfig{ - Enabled: false, - APIKey: apiKey, - APISecret: apiSecret, + b.SetDefaults() + b.Setup(bConfig) + + if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) || + b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 || + len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + t.Error("Test Failed - BTCC Setup values not set correctly") + } + + bConfig.Enabled = false + b.Setup(bConfig) + + if b.IsEnabled() { + t.Error("Test failed - BTCC TestSetup incorrect value") } - b.Setup(conf) } func TestGetFee(t *testing.T) { diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index ca7ded8b..bc0e9776 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -3,6 +3,7 @@ package btcmarkets import ( "net/url" "testing" + "time" "github.com/thrasher-/gocryptotrader/config" ) @@ -20,16 +21,31 @@ func TestSetDefaults(t *testing.T) { } func TestSetup(t *testing.T) { - conf := config.ExchangeConfig{} - bm.Setup(conf) - - conf = config.ExchangeConfig{ - APIKey: apiKey, - APISecret: apiSecret, - Enabled: true, - AuthenticatedAPISupport: true, + t.Parallel() + b := BTCMarkets{} + b.Name = "BTC Markets" + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + bConfig, err := cfg.GetExchangeConfig("BTC Markets") + if err != nil { + t.Error("Test Failed - BTC Markets Setup() init error") + } + + b.SetDefaults() + b.Setup(bConfig) + + if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) || + b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 || + len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { + t.Error("Test Failed - BTC Markets Setup values not set correctly") + } + + bConfig.Enabled = false + b.Setup(bConfig) + + if b.IsEnabled() { + t.Error("Test failed - BTC Markets TestSetup incorrect value") } - bm.Setup(conf) } func TestGetFee(t *testing.T) { diff --git a/testdata/configtest.dat b/testdata/configtest.dat index 731df5bc..48c98e84 100644 --- a/testdata/configtest.dat +++ b/testdata/configtest.dat @@ -113,6 +113,27 @@ "Uppercase": true } }, + { + "Name": "Bittrex", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-VRC,BTC-CURE,BTC-XBB,BTC-XMR,BTC-CLOAK,BTC-START,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-BTCD,BTC-VIA,BTC-UNO,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BLITZ,BTC-BAY,BTC-BTS,BTC-FAIR,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-SNRG,BTC-PKB,BTC-CPC,BTC-AEON,BTC-ETH,BTC-GCR,BTC-TX,BTC-BCY,BTC-EXP,BTC-INFX,BTC-OMNI,BTC-AMP,BTC-AGRS,BTC-XLM,BTC-BTA,USDT-BTC,BTC-CLUB,BTC-VOX,BTC-EMC,BTC-FCT,BTC-MAID,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-SAFEX,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-XVC,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-PDC,BTC-BRK,BTC-DGD,ETH-DGD,BTC-WAVES,BTC-RISE,BTC-LBC,BTC-SBD,BTC-BRX,BTC-DRACO,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-TRIG,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-XAUR,BTC-SNGLS,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-DAR,BTC-GOLOS,BTC-HKG,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-TIME,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-SNGLS,ETH-GNO,BTC-APX,BTC-TKN,ETH-TKN,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,ETH-1ST,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-MYST,ETH-MYST,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-TIME,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-FUN,ETH-FUN,BTC-PAY,ETH-PAY,BTC-MTL,ETH-MTL,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,ETH-BTS", + "EnabledPairs": "USDT-BTC", + "BaseCurrencies": "USD", + "ConfigCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + }, + "RequestCurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + } + }, { "Name": "BTCC", "Enabled": true, From 8a2c7c03ebf479743ee70a98c9670ddbc61114e8 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 29 Aug 2017 16:47:12 +1000 Subject: [PATCH 07/32] Clarify HTTP RESTful service --- main.go | 4 ++-- restful_router.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index d7a1e47c..592755c6 100644 --- a/main.go +++ b/main.go @@ -195,7 +195,7 @@ func main() { } else { listenAddr := bot.config.Webserver.ListenAddress log.Printf( - "HTTP Webserver support enabled. Listen URL: http://%s:%d/\n", + "HTTP RESTful Webserver support enabled. Listen URL: http://%s:%d/\n", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr), ) router := NewRouter(bot.exchanges) @@ -203,7 +203,7 @@ func main() { } } if !bot.config.Webserver.Enabled { - log.Println("HTTP Webserver support disabled.") + log.Println("HTTP RESTful Webserver support disabled.") } <-bot.shutdown diff --git a/restful_router.go b/restful_router.go index 350ba9c9..d6092671 100644 --- a/restful_router.go +++ b/restful_router.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" "github.com/gorilla/mux" @@ -15,6 +16,7 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { allRoutes = append(allRoutes, ConfigRoutes...) allRoutes = append(allRoutes, PortfolioRoutes...) allRoutes = append(allRoutes, WalletRoutes...) + allRoutes = append(allRoutes, IndexRoute...) for _, route := range allRoutes { var handler http.Handler handler = route.HandlerFunc @@ -28,3 +30,18 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { } return router } + +func getIndex(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "GoCryptoTrader RESTful interface. For the web GUI, please visit the web GUI readme.") + w.WriteHeader(http.StatusOK) +} + +// IndexRoute maps the index route to the getIndex function +var IndexRoute = Routes{ + Route{ + "", + "GET", + "/", + getIndex, + }, +} From 9671368f596c2b33253a374294f2ab5fb0838dc4 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 31 Aug 2017 15:58:08 +1000 Subject: [PATCH 08/32] Disable BTC-e until new exchange comes online --- config_example.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_example.dat b/config_example.dat index 401ce0ad..4e131c0f 100644 --- a/config_example.dat +++ b/config_example.dat @@ -155,7 +155,7 @@ }, { "Name": "BTCE", - "Enabled": true, + "Enabled": false, "Verbose": false, "Websocket": false, "RESTPollingDelay": 10, From 19331a13d413c9d38f45048e7b2f39b6305e93e9 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Fri, 8 Sep 2017 14:57:57 +1000 Subject: [PATCH 09/32] Update CONTRIBUTORS file --- CONTRIBUTORS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index dd235666..4752f566 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -7,3 +7,7 @@ Cornel - cornelk Łukasz Kurowski - crackcomm Adrian Gallagher - thrasher- Manuel Kreutz - 140am +libsora.so - if1live +Tong - tongxiaofeng +Jamie Cheng - starit +Jake - snipesjr \ No newline at end of file From 89848eb6e9e9229096488a57037aeb7e4764ec51 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 11 Sep 2017 13:58:26 +1000 Subject: [PATCH 10/32] Fix Liqui nonce value Addressed in https://github.com/thrasher-/gocryptotrader/pull/50 --- exchanges/liqui/liqui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 9c0578e5..4d610cb6 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -263,7 +263,7 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r } if l.Nonce.Get() == 0 { - l.Nonce.Set(time.Now().UnixNano()) + l.Nonce.Set(time.Now().Unix()) } else { l.Nonce.Inc() } From a1040c8d948b7767928a62d1d58c47627b026435 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 4 Apr 2017 11:05:31 +1000 Subject: [PATCH 11/32] Start websocket implementation --- exchanges/alphapoint/alphapoint_wrapper.go | 25 +- exchanges/anx/anx_wrapper.go | 17 +- exchanges/bitfinex/bitfinex_wrapper.go | 20 +- exchanges/bitstamp/bitstamp_wrapper.go | 19 +- exchanges/btcc/btcc_wrapper.go | 18 +- exchanges/btce/btce_wrapper.go | 17 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 17 +- exchanges/coinut/coinut_wrapper.go | 18 +- exchanges/exchange.go | 1 + exchanges/gdax/gdax_wrapper.go | 17 +- exchanges/gemini/gemini_wrapper.go | 18 +- exchanges/huobi/huobi_wrapper.go | 17 +- exchanges/itbit/itbit_wrapper.go | 17 +- exchanges/kraken/kraken_wrapper.go | 4 + exchanges/lakebtc/lakebtc_wrapper.go | 17 +- exchanges/liqui/liqui_wrapper.go | 4 + .../localbitcoins/localbitcoins_wrapper.go | 4 + exchanges/okcoin/okcoin_wrapper.go | 19 +- exchanges/poloniex/poloniex_wrapper.go | 17 +- main.go | 5 + restful_router.go | 1 + routines.go | 37 +++ ticker_routes.go | 43 +-- tools/websocket_client/main.go | 221 ++++++++++++++ websocket.go | 270 ++++++++++++++++++ 25 files changed, 738 insertions(+), 125 deletions(-) create mode 100644 routines.go create mode 100644 tools/websocket_client/main.go create mode 100644 websocket.go diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index e9e1f34d..dd8465df 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -1,8 +1,6 @@ package alphapoint import ( - "log" - "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" @@ -30,21 +28,32 @@ func (a *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } -// GetTickerPrice returns the current ticker price by currency pair -func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) ticker.TickerPrice { +func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := a.GetTicker(p.Pair().String()) if err != nil { - log.Println(err) - return ticker.TickerPrice{} + return tickerPrice, err } + tickerPrice.Pair = p tickerPrice.Ask = tick.Ask tickerPrice.Bid = tick.Bid - return tickerPrice + tickerPrice.Low = tick.Low + tickerPrice.High = tick.High + tickerPrice.Volume = tick.Volume + tickerPrice.Last = tick.Last + ticker.ProcessTicker(a.GetName(), p, tickerPrice) + return tickerPrice, nil +} + +func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tick, err := ticker.GetTicker(a.GetName(), p) + if err != nil { + return a.UpdateTicker(p) + } + return tick, nil } -// GetOrderbookEx returns an orderbookbase by currency pair func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(a.GetName(), p) if err == nil { diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 23e8795a..87d88824 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -27,7 +27,7 @@ func (a *ANX) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := a.GetTickerPrice(currency) + ticker, err := a.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -40,12 +40,7 @@ func (a *ANX) Run() { } } -func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(a.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (a *ANX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := a.GetTicker(p.Pair().String()) if err != nil { @@ -111,6 +106,14 @@ func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(a.GetName(), p) + if err != nil { + return a.UpdateTicker(p) + } + return tickerNew, nil +} + func (e *ANX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { return orderbook.OrderbookBase{}, nil } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 95268ce7..4ead2e3c 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -44,7 +44,7 @@ func (b *Bitfinex) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := b.GetTickerPrice(currency) + ticker, err := b.UpdateTicker(currency) if err != nil { return } @@ -56,18 +56,13 @@ func (b *Bitfinex) Run() { } } -// GetTickerPrice returns ticker information -func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tick, err := ticker.GetTicker(b.GetName(), p) - if err == nil { - return tick, nil - } - +func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tickerNew, err := b.GetTicker(p.Pair().String(), nil) if err != nil { return tickerPrice, err } + tickerPrice.Pair = p tickerPrice.Ask = tickerNew.Ask tickerPrice.Bid = tickerNew.Bid @@ -79,7 +74,14 @@ func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro return tickerPrice, nil } -// GetOrderbookEx returns orderbook information based on currency pair +func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tick, err := ticker.GetTicker(b.GetName(), p) + if err != nil { + return b.UpdateTicker(p) + } + return tick, nil +} + func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 2be02a59..285f7819 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -34,7 +34,7 @@ func (b *Bitstamp) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := b.GetTickerPrice(currency) + ticker, err := b.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -47,13 +47,7 @@ func (b *Bitstamp) Run() { } } -// GetTickerPrice returns ticker price information -func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(p.Pair().String(), false) if err != nil { @@ -71,7 +65,14 @@ func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro return tickerPrice, nil } -// GetOrderbookEx returns base orderbook information +func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tick, err := ticker.GetTicker(b.GetName(), p) + if err != nil { + return b.UpdateTicker(p) + } + return tick, nil +} + func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index bc50e887..69a7c173 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -32,7 +32,7 @@ func (b *BTCC) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := b.GetTickerPrice(currency) + ticker, err := b.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -45,18 +45,12 @@ func (b *BTCC) Run() { } } -func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (b *BTCC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return tickerPrice, err } - tickerPrice.Pair = p tickerPrice.Ask = tick.Sell tickerPrice.Bid = tick.Buy @@ -68,6 +62,14 @@ func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(b.GetName(), p) + if err != nil { + return b.UpdateTicker(p) + } + return tickerNew, nil +} + func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index 786fe23c..320e5876 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -50,9 +50,14 @@ func (b *BTCE) Run() { } } -func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { +func (b *BTCE) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice - tick, ok := b.Ticker[exchange.FormatExchangeCurrency(b.Name, p).String()] + result, err := b.GetTicker(p.Pair().String()) + if err != nil { + return tickerPrice, err + } + + tick, ok := result[p.Pair().Lower().String()] if !ok { return tickerPrice, errors.New("unable to get currency") } @@ -67,6 +72,14 @@ func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tick, err := ticker.GetTicker(b.GetName(), p) + if err != nil { + return b.UpdateTicker(p) + } + return tick, nil +} + func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 047450b6..33e4ce5f 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -57,7 +57,7 @@ func (b *BTCMarkets) Run() { for x := range pairs { curr := pairs[x] go func() { - ticker, err := b.GetTickerPrice(curr) + ticker, err := b.UpdateTicker(curr) if err != nil { return } @@ -73,13 +73,7 @@ func (b *BTCMarkets) Run() { } } -// GetTickerPrice returns ticker information -func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(p.GetFirstCurrency().String()) if err != nil { @@ -92,6 +86,13 @@ func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, er ticker.ProcessTicker(b.GetName(), p, tickerPrice) return tickerPrice, nil } +func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(b.GetName(), p) + if err != nil { + return b.UpdateTicker(p) + } + return tickerNew, nil +} // GetOrderbookEx returns orderbook base on the currency pair func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 96c65c86..47854620 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -50,7 +50,7 @@ func (c *COINUT) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := c.GetTickerPrice(currency) + ticker, err := c.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -84,12 +84,7 @@ func (c *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } -func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(c.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (c *COINUT) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.Pair().String()]) if err != nil { @@ -103,6 +98,15 @@ func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) tickerPrice.Low = tick.LowestSell ticker.ProcessTicker(c.GetName(), p, tickerPrice) return tickerPrice, nil + +} + +func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(c.GetName(), p) + if err != nil { + return c.UpdateTicker(p) + } + return tickerNew, nil } func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { diff --git a/exchanges/exchange.go b/exchanges/exchange.go index e89a56a7..ef7a2358 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -64,6 +64,7 @@ type IBotExchange interface { GetName() string IsEnabled() bool GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) + UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, error) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) GetEnabledCurrencies() []pair.CurrencyPair GetExchangeAccountInfo() (AccountInfo, error) diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 6c519990..28c6a3cd 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -48,7 +48,7 @@ func (g *GDAX) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := g.GetTickerPrice(currency) + ticker, err := g.UpdateTicker(currency) if err != nil { log.Println(err) @@ -81,12 +81,7 @@ func (g *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } -func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(g.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (g *GDAX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := g.GetTicker(exchange.FormatExchangeCurrency(g.Name, p).String()) if err != nil { @@ -108,6 +103,14 @@ func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(g.GetName(), p) + if err != nil { + return g.UpdateTicker(p) + } + return tickerNew, nil +} + func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(g.GetName(), p) if err == nil { diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 4e266d66..26277957 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -37,7 +37,7 @@ func (g *Gemini) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := g.GetTickerPrice(currency) + ticker, err := g.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -63,18 +63,12 @@ func (e *Gemini) GetExchangeAccountInfo() (exchange.AccountInfo, error) { exchangeCurrency.CurrencyName = accountBalance[i].Currency exchangeCurrency.TotalValue = accountBalance[i].Amount exchangeCurrency.Hold = accountBalance[i].Available - response.Currencies = append(response.Currencies, exchangeCurrency) } return response, nil } -func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(g.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (g *Gemini) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := g.GetTicker(p.Pair().String()) if err != nil { @@ -89,6 +83,14 @@ func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(g.GetName(), p) + if err != nil { + return g.UpdateTicker(p) + } + return tickerNew, nil +} + func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(g.GetName(), p) if err == nil { diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 9324b984..6cfad9c4 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -33,7 +33,7 @@ func (h *HUOBI) Run() { for x := range pairs { curr := pairs[x] go func() { - ticker, err := h.GetTickerPrice(curr) + ticker, err := h.UpdateTicker(curr) if err != nil { log.Println(err) return @@ -50,12 +50,7 @@ func (h *HUOBI) Run() { } } -func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(h.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (h *HUOBI) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := h.GetTicker(p.GetFirstCurrency().Lower().String()) if err != nil { @@ -72,6 +67,14 @@ func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(h.GetName(), p) + if err != nil { + return h.UpdateTicker(p) + } + return tickerNew, nil +} + func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(h.GetName(), p) if err == nil { diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index d444c024..078a4e9b 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -26,7 +26,7 @@ func (i *ItBit) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := i.GetTickerPrice(currency) + ticker, err := i.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -39,12 +39,7 @@ func (i *ItBit) Run() { } } -func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(i.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (i *ItBit) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := i.GetTicker(p.Pair().String()) if err != nil { @@ -62,6 +57,14 @@ func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(i.GetName(), p) + if err != nil { + return i.UpdateTicker(p) + } + return tickerNew, nil +} + func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(i.GetName(), p) if err == nil { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 356f7380..869ef5b3 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -57,6 +57,10 @@ func (k *Kraken) Run() { } } +func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { + return ticker.TickerPrice{}, nil +} + //This will return the TickerPrice struct when tickers are completed here.. func (k *Kraken) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 8bf35b45..1b6ca633 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -26,7 +26,7 @@ func (l *LakeBTC) Run() { pairs := l.GetEnabledCurrencies() for x := range pairs { currency := pairs[x] - ticker, err := l.GetTickerPrice(currency) + ticker, err := l.UpdateTicker(currency) if err != nil { log.Println(err) continue @@ -38,12 +38,7 @@ func (l *LakeBTC) Run() { } } -func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(l.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := l.GetTicker() if err != nil { return ticker.TickerPrice{}, err @@ -66,6 +61,14 @@ func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error return tickerPrice, nil } +func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(l.GetName(), p) + if err != nil { + return l.UpdateTicker(p) + } + return tickerNew, nil +} + func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(l.GetName(), p) if err == nil { diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 6ad4a709..9f4b39d4 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -61,6 +61,10 @@ func (l *Liqui) Run() { } } +func (l *Liqui) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { + return ticker.TickerPrice{}, nil +} + func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, ok := l.Ticker[exchange.FormatExchangeCurrency(l.Name, p).String()] diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 9d891da1..e12de734 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -39,6 +39,10 @@ func (l *LocalBitcoins) Run() { } } +func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { + return ticker.TickerPrice{}, nil +} + func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p) if err == nil { diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 6677276f..6c91e764 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -46,7 +46,7 @@ func (o *OKCoin) Run() { }() } go func() { - ticker, err := o.GetTickerPrice(curr) + ticker, err := o.UpdateTicker(curr) if err != nil { log.Println(err) return @@ -56,7 +56,7 @@ func (o *OKCoin) Run() { }() } else { go func() { - ticker, err := o.GetTickerPrice(curr) + ticker, err := o.UpdateTicker(curr) if err != nil { log.Println(err) return @@ -74,12 +74,7 @@ func (o *OKCoin) Run() { } } -func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(o.GetName(), currency) - if err == nil { - return tickerNew, nil - } - +func (o *OKCoin) UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := o.GetTicker(exchange.FormatExchangeCurrency(o.Name, currency).String()) if err != nil { @@ -96,6 +91,14 @@ func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, return tickerPrice, nil } +func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(o.GetName(), currency) + if err != nil { + return o.UpdateTicker(currency) + } + return tickerNew, nil +} + func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(o.GetName(), currency) if err == nil { diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index bccc8597..538c2270 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -32,7 +32,7 @@ func (p *Poloniex) Run() { for x := range pairs { currency := pairs[x] go func() { - ticker, err := p.GetTickerPrice(currency) + ticker, err := p.UpdateTicker(currency) if err != nil { log.Println(err) return @@ -45,13 +45,8 @@ func (p *Poloniex) Run() { } } -func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { +func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { currency := currencyPair.Pair().String() - tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair) - if err == nil { - return tickerNew, nil - } - var tickerPrice ticker.TickerPrice tick, err := p.GetTicker() if err != nil { @@ -69,6 +64,14 @@ func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.Ticker return tickerPrice, nil } +func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair) + if err != nil { + return p.UpdateTicker(currencyPair) + } + return tickerNew, nil +} + func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) { currency := currencyPair.Pair().String() ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair) diff --git a/main.go b/main.go index 592755c6..5a20e936 100644 --- a/main.go +++ b/main.go @@ -187,6 +187,11 @@ func main() { SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data) go portfolio.StartPortfolioWatcher() + log.Println("Starting websocket handler") + go WebsocketHandler() + + go TickerUpdaterRoutine() + if bot.config.Webserver.Enabled { err := bot.config.CheckWebserverConfigValues() if err != nil { diff --git a/restful_router.go b/restful_router.go index d6092671..467dd05f 100644 --- a/restful_router.go +++ b/restful_router.go @@ -17,6 +17,7 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { allRoutes = append(allRoutes, PortfolioRoutes...) allRoutes = append(allRoutes, WalletRoutes...) allRoutes = append(allRoutes, IndexRoute...) + allRoutes = append(allRoutes, WebsocketRoutes...) for _, route := range allRoutes { var handler http.Handler handler = route.HandlerFunc diff --git a/routines.go b/routines.go new file mode 100644 index 00000000..e4584fef --- /dev/null +++ b/routines.go @@ -0,0 +1,37 @@ +package main + +import ( + "log" + "time" + + "github.com/thrasher-/gocryptotrader/currency/pair" +) + +func TickerUpdaterRoutine() { + log.Println("Starting ticker updater routine") + for { + for x := range bot.exchanges { + if bot.exchanges[x].IsEnabled() { + exchangeName := bot.exchanges[x].GetName() + enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() + + for _, y := range enabledCurrencies { + currency := pair.NewCurrencyPair(y[0:3], y[3:]) + result, err := bot.exchanges[x].UpdateTicker(currency) + if err != nil { + log.Printf("failed to get %s currency", currency.Pair().String()) + continue + } + + evt := WebsocketEvent{ + Data: result, + Event: "ticker_update", + Exchange: exchangeName, + } + BroadcastWebsocketMessage(evt) + } + } + } + time.Sleep(time.Second * 10) + } +} diff --git a/ticker_routes.go b/ticker_routes.go index 3d7e1ded..7da2a1a7 100644 --- a/ticker_routes.go +++ b/ticker_routes.go @@ -10,31 +10,35 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - currency := vars["currency"] - exchangeName := vars["exchangeName"] - var response ticker.TickerPrice +func GetSpecificTicker(currency, exchangeName string) (ticker.TickerPrice, error) { + var specificTicker ticker.TickerPrice var err error for i := 0; i < len(bot.exchanges); i++ { if bot.exchanges[i] != nil { if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { - response, err = bot.exchanges[i].GetTickerPrice( + specificTicker, err = bot.exchanges[i].GetTickerPrice( pair.NewCurrencyPairFromString(currency), ) - if err != nil { - log.Println(err) - continue - } + break } } } + return specificTicker, err +} +func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + currency := vars["currency"] + exchange := vars["exchangeName"] + response, err := GetSpecificTicker(currency, exchange) + if err != nil { + log.Printf("Failed to fetch ticker for %s currency: %s\n", exchange, + currency) + return + } w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) - encoder := json.NewEncoder(w) - - if err = encoder.Encode(response); err != nil { + if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } @@ -51,8 +55,8 @@ type EnabledExchangeCurrencies struct { ExchangeValues []ticker.TickerPrice `json:"exchangeValues"` } -func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { - var response AllEnabledExchangeCurrencies +func GetAllActiveTickers() []EnabledExchangeCurrencies { + var tickerData []EnabledExchangeCurrencies for _, individualBot := range bot.exchanges { if individualBot != nil && individualBot.IsEnabled() { @@ -74,9 +78,16 @@ func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { individualExchange.ExchangeValues, tickerPrice, ) } - response.Data = append(response.Data, individualExchange) + tickerData = append(tickerData, individualExchange) } } + return tickerData +} + +func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { + var response AllEnabledExchangeCurrencies + response.Data = GetAllActiveTickers() + w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { diff --git a/tools/websocket_client/main.go b/tools/websocket_client/main.go new file mode 100644 index 00000000..509ada13 --- /dev/null +++ b/tools/websocket_client/main.go @@ -0,0 +1,221 @@ +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-/gocryptotrader/common" +) + +var ( + WSConn *websocket.Conn +) + +type WebsocketEvent struct { + Event string `json:"event"` + Data interface{} `json:"data"` + Exchange string `json:"exchange,omitempty"` +} + +type WebsocketAuth struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type WebsocketEventResponse struct { + Event string `json:"event"` + Data interface{} `json:"data"` + Error string `json:"error"` +} + +type WebsocketTickerRequest struct { + Exchange string `json:"exchangeName"` + Currency string `json:"currency"` +} + +func SendWebsocketAuth(username, password string) error { + pwHash := common.HexEncodeToString(common.GetSHA256([]byte(password))) + req := WebsocketEvent{ + Event: "auth", + Data: WebsocketAuth{ + Username: username, + Password: pwHash, + }, + } + + return SendWebsocketMsg(req) +} + +func SendWebsocketMsg(data interface{}) error { + return WSConn.WriteJSON(data) +} + +func GetWebsocketTicker(currency string) error { + wsevt := WebsocketEvent{ + Event: "ticker", + Data: currency, + } + + return SendWebsocketMsg(wsevt) +} + +func main() { + var Dialer websocket.Dialer + var err error + + WSConn, _, err = Dialer.Dial("ws://localhost:9050/ws", http.Header{}) + + if err != nil { + log.Println("Unable to connect to websocket server") + return + } + + log.Println("Connected to websocket!") + log.Println("Authenticating..") + SendWebsocketAuth("username", "password") + + var wsResp WebsocketEventResponse + err = WSConn.ReadJSON(&wsResp) + if err != nil { + log.Println(err) + return + } + + if wsResp.Error != "" { + log.Fatal(wsResp.Error) + } + + log.Println("Authenticated successfully") + log.Println("Getting config..") + + req := WebsocketEvent{ + Event: "GetConfig", + } + + err = WSConn.WriteJSON(req) + if err != nil { + log.Println(err) + return + } + + err = WSConn.ReadJSON(&wsResp) + if err != nil { + log.Println(err) + return + } + + if wsResp.Error != "" { + log.Fatal(wsResp.Error) + } + + log.Printf("Fetched config.") + + req = WebsocketEvent{ + Event: "SaveConfig", + Data: wsResp.Data, + } + + log.Println("Saving config..") + err = WSConn.WriteJSON(req) + if err != nil { + log.Fatal(err) + } + + err = WSConn.ReadJSON(&wsResp) + if err != nil { + log.Println(err) + return + } + + if wsResp.Error != "" { + log.Fatal(wsResp.Error) + } + + log.Println("Saved config!") + log.Println("Getting account info..") + + req = WebsocketEvent{ + Event: "GetAccountInfo", + } + + err = WSConn.WriteJSON(req) + if err != nil { + log.Println(err) + return + } + + err = WSConn.ReadJSON(&wsResp) + if err != nil { + log.Println(err) + return + } + + if wsResp.Error != "" { + log.Fatal(wsResp.Error) + } + + log.Println("Getting tickers..") + + req = WebsocketEvent{ + Event: "GetTickers", + } + + err = WSConn.WriteJSON(req) + if err != nil { + log.Println(err) + return + } + + err = WSConn.ReadJSON(&wsResp) + if err != nil { + log.Println(err) + return + } + + if wsResp.Error != "" { + log.Fatal(wsResp.Error) + } + + log.Println("Getting specific ticker..") + + var tickReq WebsocketTickerRequest + tickReq.Currency = "LTCUSD" + tickReq.Exchange = "Bitfinex" + + req = WebsocketEvent{ + Event: "GetTicker", + Data: tickReq, + } + + err = WSConn.WriteJSON(req) + if err != nil { + log.Println(err) + return + } + + err = WSConn.ReadJSON(&wsResp) + if err != nil { + log.Println(err) + return + } + + if wsResp.Error != "" { + log.Fatal(wsResp.Error) + } + + log.Println(wsResp) + + for { + msgType, resp, err := WSConn.ReadMessage() + if err != nil { + log.Fatal(err) + } + + log.Println(msgType) + log.Println(string(resp)) + } + time.Sleep(time.Second * 10) + WSConn.Close() +} diff --git a/websocket.go b/websocket.go new file mode 100644 index 00000000..ccdb4499 --- /dev/null +++ b/websocket.go @@ -0,0 +1,270 @@ +package main + +import ( + "errors" + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" +) + +const ( + WebsocketResponseSuccess = "OK" +) + +var WebsocketRoutes = Routes{ + Route{ + "ws", + "GET", + "/ws", + WebsocketClientHandler, + }, +} + +type WebsocketClient struct { + ID int + Conn *websocket.Conn + LastRecv time.Time + Authenticated bool +} + +type WebsocketEvent struct { + Exchange string `json:"exchange,omitempty"` + Event string + Data interface{} +} + +type WebsocketEventResponse struct { + Event string `json:"event"` + Data interface{} `json:"data"` + Error string `json:"error"` +} + +type WebsocketTickerRequest struct { + Exchange string `json:"exchangeName"` + Currency string `json:"currency"` +} + +var WebsocketClientHub []WebsocketClient + +func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { + upgrader := websocket.Upgrader{ + WriteBufferSize: 1024, + ReadBufferSize: 1024, + } + + newClient := WebsocketClient{ + ID: len(WebsocketClientHub), + } + + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + newClient.Conn = conn + WebsocketClientHub = append(WebsocketClientHub, newClient) + log.Println("New websocket client connected.") +} + +func DisconnectWebsocketClient(id int, err error) { + for i := range WebsocketClientHub { + if WebsocketClientHub[i].ID == id { + WebsocketClientHub[i].Conn.Close() + WebsocketClientHub = append(WebsocketClientHub[:i], WebsocketClientHub[i+1:]...) + log.Printf("Disconnected Websocket client, error: %s", err) + return + } + } +} + +func SendWebsocketMessage(id int, data interface{}) error { + for _, x := range WebsocketClientHub { + if x.ID == id { + return x.Conn.WriteJSON(data) + } + } + return nil +} + +func BroadcastWebsocketMessage(evt WebsocketEvent) error { + log.Println(evt) + for _, x := range WebsocketClientHub { + x.Conn.WriteJSON(evt) + } + return nil +} + +type WebsocketAuth struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func WebsocketHandler() { + for { + for x := range WebsocketClientHub { + msgType, msg, err := WebsocketClientHub[x].Conn.ReadMessage() + if err != nil { + DisconnectWebsocketClient(x, err) + continue + } + + if msgType != websocket.TextMessage { + DisconnectWebsocketClient(x, err) + continue + } + + var evt WebsocketEvent + err = common.JSONDecode(msg, &evt) + if err != nil { + log.Println(err) + continue + } + + if evt.Event == "" { + DisconnectWebsocketClient(x, errors.New("Websocket client sent data we did not understand")) + continue + } + + dataJSON, err := common.JSONEncode(evt.Data) + if err != nil { + log.Println(err) + continue + } + + if !WebsocketClientHub[x].Authenticated && evt.Event != "auth" { + wsResp := WebsocketEventResponse{ + Event: "auth", + Error: "you must authenticate first", + } + SendWebsocketMessage(x, wsResp) + DisconnectWebsocketClient(x, errors.New("Websocket client did not auth")) + continue + } else if !WebsocketClientHub[x].Authenticated && evt.Event == "auth" { + var auth WebsocketAuth + err = common.JSONDecode(dataJSON, &auth) + if err != nil { + log.Println(err) + continue + } + hashPW := common.HexEncodeToString(common.GetSHA256([]byte("password"))) + if auth.Username == "username" && auth.Password == hashPW { + WebsocketClientHub[x].Authenticated = true + wsResp := WebsocketEventResponse{ + Event: "auth", + Data: WebsocketResponseSuccess, + } + SendWebsocketMessage(x, wsResp) + log.Println("Websocket client authenticated successfully") + continue + } else { + wsResp := WebsocketEventResponse{ + Event: "auth", + Error: "invalid username/password", + } + SendWebsocketMessage(x, wsResp) + DisconnectWebsocketClient(x, errors.New("Websocket client sent wrong username/password")) + continue + } + } + switch evt.Event { + case "GetConfig": + wsResp := WebsocketEventResponse{ + Event: "GetConfig", + Data: bot.config, + } + SendWebsocketMessage(x, wsResp) + continue + case "SaveConfig": + wsResp := WebsocketEventResponse{ + Event: "SaveConfig", + } + var cfg config.Config + err := common.JSONDecode(dataJSON, &cfg) + if err != nil { + wsResp.Error = err.Error() + SendWebsocketMessage(x, wsResp) + log.Println(err) + continue + } + + //Save change the settings + for x := range bot.config.Exchanges { + for i := 0; i < len(cfg.Exchanges); i++ { + if cfg.Exchanges[i].Name == bot.config.Exchanges[x].Name { + bot.config.Exchanges[x].Enabled = cfg.Exchanges[i].Enabled + bot.config.Exchanges[x].APIKey = cfg.Exchanges[i].APIKey + bot.config.Exchanges[x].APISecret = cfg.Exchanges[i].APISecret + bot.config.Exchanges[x].EnabledPairs = cfg.Exchanges[i].EnabledPairs + } + } + } + + //Reload the configuration + err = bot.config.SaveConfig(bot.configFile) + if err != nil { + wsResp.Error = err.Error() + SendWebsocketMessage(x, wsResp) + continue + } + err = bot.config.LoadConfig(bot.configFile) + if err != nil { + wsResp.Error = err.Error() + SendWebsocketMessage(x, wsResp) + continue + } + setupBotExchanges() + wsResp.Data = WebsocketResponseSuccess + SendWebsocketMessage(x, wsResp) + continue + case "GetAccountInfo": + accountInfo := GetAllEnabledExchangeAccountInfo() + wsResp := WebsocketEventResponse{ + Event: "GetAccountInfo", + Data: accountInfo, + } + SendWebsocketMessage(x, wsResp) + continue + case "GetTicker": + wsResp := WebsocketEventResponse{ + Event: "GetTicker", + } + var tickerReq WebsocketTickerRequest + err := common.JSONDecode(dataJSON, &tickerReq) + if err != nil { + wsResp.Error = err.Error() + SendWebsocketMessage(x, wsResp) + log.Println(err) + continue + } + + data, err := GetSpecificTicker(tickerReq.Currency, + tickerReq.Exchange) + + if err != nil { + wsResp.Error = err.Error() + SendWebsocketMessage(x, wsResp) + log.Println(err) + continue + } + wsResp.Data = data + SendWebsocketMessage(x, wsResp) + continue + + case "GetTickers": + wsResp := WebsocketEventResponse{ + Event: "GetTickers", + } + tickers := GetAllActiveTickers() + wsResp.Data = tickers + SendWebsocketMessage(x, wsResp) + continue + } + } + time.Sleep(time.Millisecond) + } +} From a5b3a6b48976bf3abcc1b83f0de03c59bc87cd36 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 29 Aug 2017 10:24:32 +1000 Subject: [PATCH 12/32] Add demo routines function to fetch all exchange tickers --- common/common.go | 1 + exchanges/bitfinex/bitfinex_wrapper.go | 20 +----- exchanges/bitstamp/bitstamp_wrapper.go | 19 ----- exchanges/bittrex/bittrex_wrapper.go | 35 +++------ exchanges/btcc/btcc_wrapper.go | 19 ----- exchanges/btcmarkets/btcmarkets_wrapper.go | 23 ------ exchanges/coinut/coinut_wrapper.go | 19 ----- exchanges/gdax/gdax_wrapper.go | 20 ------ exchanges/gemini/gemini_wrapper.go | 19 ----- exchanges/huobi/huobi_wrapper.go | 24 ------- exchanges/itbit/itbit_wrapper.go | 19 ----- exchanges/kraken/kraken_wrapper.go | 69 +++++++++--------- exchanges/lakebtc/lakebtc_wrapper.go | 17 ----- exchanges/liqui/liqui_wrapper.go | 72 ++++++++----------- .../localbitcoins/localbitcoins_wrapper.go | 56 ++++++--------- exchanges/okcoin/okcoin_wrapper.go | 24 ------- exchanges/poloniex/poloniex_wrapper.go | 19 ----- routines.go | 16 ++++- 18 files changed, 109 insertions(+), 382 deletions(-) diff --git a/common/common.go b/common/common.go index 0bf1791e..aa762ead 100644 --- a/common/common.go +++ b/common/common.go @@ -302,6 +302,7 @@ func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { if res.StatusCode != 200 { log.Printf("HTTP status code: %d\n", res.StatusCode) + log.Printf("URL: %s\n", url) return errors.New("status code was not 200") } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 4ead2e3c..7ee90ddb 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -2,13 +2,11 @@ package bitfinex import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -38,24 +36,9 @@ func (b *Bitfinex) Run() { log.Printf("%s Failed to get config.\n", b.GetName()) } } - - for b.Enabled { - pairs := b.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := b.UpdateTicker(currency) - if err != nil { - return - } - log.Printf("Bitfinex %s Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * b.RESTPollingDelay) - } } +// UpdateTicker updates and returns the ticker func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tickerNew, err := b.GetTicker(p.Pair().String(), nil) @@ -74,6 +57,7 @@ func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +// GetTickerPrice returns the ticker func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(b.GetName(), p) if err != nil { diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 285f7819..c166b4db 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -2,13 +2,11 @@ package bitstamp import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -28,23 +26,6 @@ func (b *Bitstamp) Run() { if b.Websocket { go b.PusherClient() } - - for b.Enabled { - pairs := b.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := b.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("Bitstamp %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * b.RESTPollingDelay) - } } func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 645ddf30..a7292076 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -2,13 +2,11 @@ package bittrex import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -54,23 +52,6 @@ func (b *Bittrex) Run() { log.Printf("%s Failed to get config.\n", b.GetName()) } } - - for b.Enabled { - pairs := b.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := b.GetTickerPrice(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("Bittrex %s Last %f Bid %f Ask %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume) - stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * b.RESTPollingDelay) - } } //GetExchangeAccountInfo Retrieves balances for all enabled currencies for the Bittrexexchange @@ -92,13 +73,7 @@ func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } -// GetTickerPrice returns the ticker for a currencyp pair -func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p) - if err == nil { - return tickerNew, nil - } - +func (b *Bittrex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetMarketSummary(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { @@ -113,6 +88,14 @@ func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error return tickerPrice, nil } +func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tick, err := ticker.GetTicker(b.GetName(), p) + if err != nil { + return b.UpdateTicker(p) + } + return tick, nil +} + // GetOrderbookEx returns the orderbook for a currencyp pair func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index 69a7c173..6a621925 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -2,13 +2,11 @@ package btcc import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -26,23 +24,6 @@ func (b *BTCC) Run() { if b.Websocket { go b.WebsocketClient() } - - for b.Enabled { - pairs := b.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := b.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("BTCC %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * b.RESTPollingDelay) - } } func (b *BTCC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 33e4ce5f..3e85669b 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -2,15 +2,12 @@ package btcmarkets import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -51,26 +48,6 @@ func (b *BTCMarkets) Run() { return } } - - for b.Enabled { - pairs := b.GetEnabledCurrencies() - for x := range pairs { - curr := pairs[x] - go func() { - ticker, err := b.UpdateTicker(curr) - if err != nil { - return - } - BTCMarketsLastUSD, _ := currency.ConvertCurrency(ticker.Last, "AUD", "USD") - BTCMarketsBestBidUSD, _ := currency.ConvertCurrency(ticker.Bid, "AUD", "USD") - BTCMarketsBestAskUSD, _ := currency.ConvertCurrency(ticker.Ask, "AUD", "USD") - log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", exchange.FormatCurrency(curr).String(), BTCMarketsLastUSD, ticker.Last, BTCMarketsBestBidUSD, ticker.Bid, BTCMarketsBestAskUSD, ticker.Ask) - stats.AddExchangeInfo(b.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, 0) - stats.AddExchangeInfo(b.GetName(), curr.GetFirstCurrency().String(), "USD", BTCMarketsLastUSD, 0) - }() - } - time.Sleep(time.Second * b.RESTPollingDelay) - } } func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 47854620..32621e94 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -2,13 +2,11 @@ package coinut import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -44,23 +42,6 @@ func (c *COINUT) Run() { if err != nil { log.Printf("%s Failed to get config.\n", c.GetName()) } - - for c.Enabled { - pairs := c.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := c.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("COINUT %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(c.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * c.RESTPollingDelay) - } } // GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the COINUT exchange diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 28c6a3cd..6895b669 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -2,13 +2,11 @@ package gdax import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -42,24 +40,6 @@ func (g *GDAX) Run() { log.Printf("%s Failed to get config.\n", g.GetName()) } } - - for g.Enabled { - pairs := g.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := g.UpdateTicker(currency) - - if err != nil { - log.Println(err) - return - } - log.Printf("GDAX %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(g.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * g.RESTPollingDelay) - } } //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the GDAX exchange diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 26277957..653979fe 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -3,12 +3,10 @@ package gemini import ( "log" "net/url" - "time" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -31,23 +29,6 @@ func (g *Gemini) Run() { log.Printf("%s Failed to get config.\n", g.GetName()) } } - - for g.Enabled { - pairs := g.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := g.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("Gemini %s Last %f Bid %f Ask %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume) - stats.AddExchangeInfo(g.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * g.RESTPollingDelay) - } } //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Gemini exchange diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 6cfad9c4..09432302 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -2,14 +2,11 @@ package huobi import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -27,27 +24,6 @@ func (h *HUOBI) Run() { if h.Websocket { go h.WebsocketClient() } - - for h.Enabled { - pairs := h.GetEnabledCurrencies() - for x := range pairs { - curr := pairs[x] - go func() { - ticker, err := h.UpdateTicker(curr) - if err != nil { - log.Println(err) - return - } - HuobiLastUSD, _ := currency.ConvertCurrency(ticker.Last, "CNY", "USD") - HuobiHighUSD, _ := currency.ConvertCurrency(ticker.High, "CNY", "USD") - HuobiLowUSD, _ := currency.ConvertCurrency(ticker.Low, "CNY", "USD") - log.Printf("Huobi %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", exchange.FormatCurrency(curr).String(), HuobiLastUSD, ticker.Last, HuobiHighUSD, ticker.High, HuobiLowUSD, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(h.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - stats.AddExchangeInfo(h.GetName(), curr.GetFirstCurrency().String(), "USD", HuobiLastUSD, ticker.Volume) - }() - } - time.Sleep(time.Second * h.RESTPollingDelay) - } } func (h *HUOBI) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 078a4e9b..702a75e2 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -3,12 +3,10 @@ package itbit import ( "log" "strconv" - "time" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -20,23 +18,6 @@ func (i *ItBit) Run() { log.Printf("%s polling delay: %ds.\n", i.GetName(), i.RESTPollingDelay) log.Printf("%s %d currencies enabled: %s.\n", i.GetName(), len(i.EnabledPairs), i.EnabledPairs) } - - for i.Enabled { - pairs := i.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := i.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("ItBit %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(i.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * i.RESTPollingDelay) - } } func (i *ItBit) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 869ef5b3..3b27b576 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -2,12 +2,10 @@ package kraken import ( "log" - "time" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -34,46 +32,47 @@ func (k *Kraken) Run() { log.Printf("%s Failed to get config.\n", k.GetName()) } } - - for k.Enabled { - pairs := k.GetEnabledCurrencies() - pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs) - if err != nil { - log.Println(err) - continue - } - err = k.GetTicker(pairsCollated.String()) - if err != nil { - log.Println(err) - } else { - for _, x := range pairs { - ticker := k.Ticker[x.Pair().String()] - log.Printf("Kraken %s Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(x).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(k.GetName(), x.GetFirstCurrency().String(), x.GetSecondCurrency().String(), - ticker.Last, ticker.Volume) - } - } - time.Sleep(time.Second * k.RESTPollingDelay) - } } func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - return ticker.TickerPrice{}, nil + var tickerPrice ticker.TickerPrice + + pairs := k.GetEnabledCurrencies() + pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs) + if err != nil { + return tickerPrice, err + } + err = k.GetTicker(pairsCollated.String()) + if err != nil { + return tickerPrice, err + } + + for _, x := range pairs { + var tp ticker.TickerPrice + tick, ok := k.Ticker[x.Pair().String()] + if !ok { + continue + } + + tp.Pair = x + tp.Last = tick.Last + tp.Ask = tick.Ask + tp.Bid = tick.Bid + tp.High = tick.High + tp.Low = tick.Low + tp.Volume = tick.Volume + ticker.ProcessTicker(k.GetName(), x, tp) + } + return ticker.GetTicker(k.GetName(), p) } //This will return the TickerPrice struct when tickers are completed here.. func (k *Kraken) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice - /* - ticker, err := i.GetTicker(currency) - if err != nil { - log.Println(err) - return tickerPrice - } - tickerPrice.Ask = ticker.Ask - tickerPrice.Bid = ticker.Bid - */ - return tickerPrice, nil + tickerNew, err := ticker.GetTicker(k.GetName(), p) + if err != nil { + return k.UpdateTicker(p) + } + return tickerNew, nil } func (k *Kraken) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 1b6ca633..12bb72c9 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -3,13 +3,11 @@ package lakebtc import ( "log" "strconv" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -21,21 +19,6 @@ func (l *LakeBTC) Run() { log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) log.Printf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs) } - - for l.Enabled { - pairs := l.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - ticker, err := l.UpdateTicker(currency) - if err != nil { - log.Println(err) - continue - } - log.Printf("LakeBTC %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - } - time.Sleep(time.Second * l.RESTPollingDelay) - } } func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 9f4b39d4..74cd9d43 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -1,15 +1,12 @@ package liqui import ( - "errors" "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -34,52 +31,43 @@ func (l *Liqui) Run() { log.Printf("%s Failed to get config.\n", l.GetName()) } } - - pairsString, err := exchange.GetAndFormatExchangeCurrencies(l.Name, - l.GetEnabledCurrencies()) - if err != nil { - log.Println(err) - l.Enabled = false - return - } - - for l.Enabled { - go func() { - ticker, err := l.GetTicker(pairsString.String()) - if err != nil { - log.Println(err) - return - } - for x, y := range ticker { - currency := pair.NewCurrencyPairDelimiter(common.StringToUpper(x), "_") - log.Printf("Liqui %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), y.Last, y.High, y.Low, y.Vol_cur) - l.Ticker[x] = y - stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), y.Last, y.Vol_cur) - } - }() - time.Sleep(time.Second * l.RESTPollingDelay) - } } func (l *Liqui) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - return ticker.TickerPrice{}, nil + var tickerPrice ticker.TickerPrice + pairsString, err := exchange.GetAndFormatExchangeCurrencies(l.Name, + l.GetEnabledCurrencies()) + if err != nil { + return tickerPrice, err + } + + result, err := l.GetTicker(pairsString.String()) + if err != nil { + return tickerPrice, err + } + + for x, y := range result { + var tp ticker.TickerPrice + currency := pair.NewCurrencyPairDelimiter(common.StringToUpper(x), "_") + tp.Pair = currency + tp.Last = y.Last + tp.Ask = y.Sell + tp.Bid = y.Buy + tp.Last = y.Last + tp.Low = y.Low + tp.Volume = y.Vol_cur + ticker.ProcessTicker(l.GetName(), currency, tp) + } + + return ticker.GetTicker(l.GetName(), p) } func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice - tick, ok := l.Ticker[exchange.FormatExchangeCurrency(l.Name, p).String()] - if !ok { - return tickerPrice, errors.New("unable to get currency") + tickerNew, err := ticker.GetTicker(l.GetName(), p) + if err != nil { + return l.UpdateTicker(p) } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Buy - tickerPrice.Bid = tick.Sell - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Vol_cur - tickerPrice.High = tick.High - ticker.ProcessTicker(l.GetName(), p, tickerPrice) - return tickerPrice, nil + return tickerNew, nil } func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index e12de734..3712389d 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -2,12 +2,12 @@ package localbitcoins import ( "log" - "time" + + "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -20,49 +20,33 @@ func (l *LocalBitcoins) Run() { log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) log.Printf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs) } - - for l.Enabled { - pairs := l.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - ticker, err := l.GetTickerPrice(currency) - - if err != nil { - log.Println(err) - return - } - - log.Printf("LocalBitcoins BTC %s: Last %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.Volume) - stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - } - time.Sleep(time.Second * l.RESTPollingDelay) - } } func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - return ticker.TickerPrice{}, nil + var tickerPrice ticker.TickerPrice + tick, err := l.GetTicker() + if err != nil { + return tickerPrice, err + } + + for key, value := range tick { + currency := pair.NewCurrencyPair("BTC", common.StringToUpper(key)) + var tp ticker.TickerPrice + tp.Pair = currency + tp.Last = value.Rates.Last + tp.Volume = value.VolumeBTC + ticker.ProcessTicker(l.GetName(), currency, tp) + } + + return ticker.GetTicker(l.GetName(), p) } func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p) if err == nil { - return tickerNew, nil + return l.UpdateTicker(p) } - - tick, err := l.GetTicker() - if err != nil { - return ticker.TickerPrice{}, err - } - - var tickerPrice ticker.TickerPrice - for key, value := range tick { - tickerPrice.Pair = p - tickerPrice.Last = value.Rates.Last - tickerPrice.Pair.SecondCurrency = pair.CurrencyItem(key) - tickerPrice.Volume = value.VolumeBTC - ticker.ProcessTicker(l.GetName(), p, tickerPrice) - } - return tickerPrice, nil + return tickerNew, nil } func (l *LocalBitcoins) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 6c91e764..ae05ffde 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -5,7 +5,6 @@ import ( "time" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" @@ -45,29 +44,6 @@ func (o *OKCoin) Run() { stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Vol) }() } - go func() { - ticker, err := o.UpdateTicker(curr) - if err != nil { - log.Println(err) - return - } - log.Printf("OKCoin Intl Spot %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(curr).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } else { - go func() { - ticker, err := o.UpdateTicker(curr) - if err != nil { - log.Println(err) - return - } - tickerLastUSD, _ := currency.ConvertCurrency(ticker.Last, "CNY", "USD") - tickerHighUSD, _ := currency.ConvertCurrency(ticker.High, "CNY", "USD") - tickerLowUSD, _ := currency.ConvertCurrency(ticker.Low, "CNY", "USD") - log.Printf("OKCoin China %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", exchange.FormatCurrency(curr).String(), tickerLastUSD, ticker.Last, tickerHighUSD, ticker.High, tickerLowUSD, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), "USD", tickerLastUSD, ticker.Volume) - }() } } time.Sleep(time.Second * o.RESTPollingDelay) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 538c2270..1e308de0 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -2,13 +2,11 @@ package poloniex import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -26,23 +24,6 @@ func (p *Poloniex) Run() { if p.Websocket { go p.WebsocketClient() } - - for p.Enabled { - pairs := p.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := p.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("Poloniex %s Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(p.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * p.RESTPollingDelay) - } } func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { diff --git a/routines.go b/routines.go index e4584fef..52afd11a 100644 --- a/routines.go +++ b/routines.go @@ -4,7 +4,7 @@ import ( "log" "time" - "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" ) func TickerUpdaterRoutine() { @@ -15,14 +15,24 @@ func TickerUpdaterRoutine() { exchangeName := bot.exchanges[x].GetName() enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() - for _, y := range enabledCurrencies { - currency := pair.NewCurrencyPair(y[0:3], y[3:]) + for y := range enabledCurrencies { + currency := enabledCurrencies[y] result, err := bot.exchanges[x].UpdateTicker(currency) if err != nil { log.Printf("failed to get %s currency", currency.Pair().String()) continue } + log.Printf("%s %s: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", + exchangeName, + exchange.FormatCurrency(currency).String(), + result.Last, + result.Ask, + result.Bid, + result.High, + result.Low, + result.Volume) + evt := WebsocketEvent{ Data: result, Event: "ticker_update", From ad7ae88ff4d8b2b199eda2db115511720dd393fc Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 30 Aug 2017 16:12:18 +1000 Subject: [PATCH 13/32] Add UpdateOrderbook exchange function and update all exchanges to support it --- exchanges/alphapoint/alphapoint_wrapper.go | 19 ++++--- exchanges/anx/anx_wrapper.go | 23 +++++++-- exchanges/bitfinex/bitfinex_wrapper.go | 15 ++++-- exchanges/bitstamp/bitstamp_wrapper.go | 13 +++-- exchanges/bittrex/bittrex_wrapper.go | 15 ++++-- exchanges/btcc/btcc_wrapper.go | 19 +++++-- exchanges/btce/btce_wrapper.go | 24 ++++++--- exchanges/btcmarkets/btcmarkets_wrapper.go | 13 +++-- exchanges/coinut/coinut_wrapper.go | 15 +++++- exchanges/exchange.go | 1 + exchanges/gdax/gdax_wrapper.go | 15 +++++- exchanges/gemini/gemini_wrapper.go | 20 ++++++-- exchanges/huobi/huobi_wrapper.go | 20 ++++++-- exchanges/itbit/itbit_wrapper.go | 17 +++++-- exchanges/kraken/kraken.go | 50 +++++++++++++++++-- exchanges/kraken/kraken_types.go | 12 +++++ exchanges/kraken/kraken_wrapper.go | 42 +++++++++++++--- exchanges/lakebtc/lakebtc_wrapper.go | 14 +++++- exchanges/liqui/liqui_wrapper.go | 21 ++++++-- .../localbitcoins/localbitcoins_wrapper.go | 43 ++++++++++++++-- exchanges/okcoin/okcoin_wrapper.go | 20 ++++++-- exchanges/poloniex/poloniex_wrapper.go | 25 +++++++--- main.go | 1 + routines.go | 36 ++++++++++++- 24 files changed, 404 insertions(+), 89 deletions(-) diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index dd8465df..8ec18e8d 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -28,6 +28,7 @@ func (a *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } +// UpdateTicker updates and returns the ticker for a currency pair func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := a.GetTicker(p.Pair().String()) @@ -46,6 +47,7 @@ func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, erro return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(a.GetName(), p) if err != nil { @@ -54,12 +56,8 @@ func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, er return tick, nil } -func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(a.GetName(), p) - if err == nil { - return ob, nil - } - +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (a *Alphapoint) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := a.GetOrderbook(p.Pair().String()) if err != nil { @@ -80,3 +78,12 @@ func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBas orderbook.ProcessOrderbook(a.GetName(), p, orderBook) return orderBook, nil } + +// GetOrderbookEx returns the orderbook for a currency pair +func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { + ob, err := orderbook.GetOrderbook(a.GetName(), p) + if err == nil { + return a.UpdateOrderbook(p) + } + return ob, nil +} diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 87d88824..e6288c5d 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -12,10 +12,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the ANX go routine func (a *ANX) Start() { go a.Run() } +// Run implements the ANX wrapper func (a *ANX) Run() { if a.Verbose { log.Printf("%s polling delay: %ds.\n", a.GetName(), a.RESTPollingDelay) @@ -40,6 +42,7 @@ func (a *ANX) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (a *ANX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := a.GetTicker(p.Pair().String()) @@ -106,6 +109,7 @@ func (a *ANX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(a.GetName(), p) if err != nil { @@ -114,13 +118,24 @@ func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerNew, nil } -func (e *ANX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - return orderbook.OrderbookBase{}, nil +// GetOrderbookEx returns the orderbook for a currency pair +func (a *ANX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { + ob, err := orderbook.GetOrderbook(a.GetName(), p) + if err == nil { + return a.UpdateOrderbook(p) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (a *ANX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { + var orderBook orderbook.OrderbookBase + return orderBook, nil } //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the ANX exchange -func (e *ANX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +func (a *ANX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() + response.ExchangeName = a.GetName() return response, nil } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 7ee90ddb..d09f1020 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -10,12 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -// Start starts a new wrapper through a go routine +// Start starts the Bitfinex go routine func (b *Bitfinex) Start() { go b.Run() } -// Run starts a new websocketclient connection and monitors ticker information +// Run implements the Bitfinex wrapper func (b *Bitfinex) Run() { if b.Verbose { log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) @@ -38,7 +38,7 @@ func (b *Bitfinex) Run() { } } -// UpdateTicker updates and returns the ticker +// UpdateTicker updates and returns the ticker for a currency pair func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tickerNew, err := b.GetTicker(p.Pair().String(), nil) @@ -57,7 +57,7 @@ func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } -// GetTickerPrice returns the ticker +// GetTickerPrice returns the ticker for a currency pair func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(b.GetName(), p) if err != nil { @@ -66,12 +66,17 @@ func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro return tick, nil } +// GetOrderbookEx returns the orderbook for a currency pair func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { - return ob, nil + return b.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := b.GetOrderbook(p.Pair().String(), nil) if err != nil { diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index c166b4db..2e8609ed 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -10,12 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -// Start starts a new go routine run +// Start starts the Bitstamp go routine func (b *Bitstamp) Start() { go b.Run() } -// Run starts a new websocket connection runs a new go routine pusher +// Run implements the Bitstamp wrapper func (b *Bitstamp) Run() { if b.Verbose { log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) @@ -28,6 +28,7 @@ func (b *Bitstamp) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(p.Pair().String(), false) @@ -46,6 +47,7 @@ func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(b.GetName(), p) if err != nil { @@ -54,12 +56,17 @@ func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro return tick, nil } +// GetOrderbookEx returns the orderbook for a currency pair func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { - return ob, nil + return b.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := b.GetOrderbook(p.Pair().String()) if err != nil { diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index a7292076..72797a54 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -10,7 +10,7 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -// Start stats the Bittrex go routine +// Start starts the Bittrex go routine func (b *Bittrex) Start() { go b.Run() } @@ -54,7 +54,8 @@ func (b *Bittrex) Run() { } } -//GetExchangeAccountInfo Retrieves balances for all enabled currencies for the Bittrexexchange +// GetExchangeAccountInfo Retrieves balances for all enabled currencies for the +// Bittrex exchange func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.ExchangeName = b.GetName() @@ -73,6 +74,7 @@ func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } +// UpdateTicker updates and returns the ticker for a currency pair func (b *Bittrex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetMarketSummary(exchange.FormatExchangeCurrency(b.GetName(), p).String()) @@ -88,6 +90,7 @@ func (b *Bittrex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(b.GetName(), p) if err != nil { @@ -96,13 +99,17 @@ func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error return tick, nil } -// GetOrderbookEx returns the orderbook for a currencyp pair +// GetOrderbookEx returns the orderbook for a currency pair func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { - return ob, nil + return b.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := b.GetOrderbook(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index 6a621925..b009339d 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the BTCC go routine func (b *BTCC) Start() { go b.Run() } +// Run implements the BTCC wrapper func (b *BTCC) Run() { if b.Verbose { log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) @@ -26,6 +28,7 @@ func (b *BTCC) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (b *BTCC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(exchange.FormatExchangeCurrency(b.GetName(), p).String()) @@ -43,6 +46,7 @@ func (b *BTCC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p) if err != nil { @@ -51,12 +55,17 @@ func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerNew, nil } +// GetOrderbookEx returns the orderbook for a currency pair func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { - return ob, nil + return b.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *BTCC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := b.GetOrderBook(exchange.FormatExchangeCurrency(b.GetName(), p).String(), 100) if err != nil { @@ -65,12 +74,12 @@ func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(ob.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(ob.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } orderBook.Pair = p @@ -78,8 +87,8 @@ func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err return orderBook, nil } -//TODO: Retrieve BTCC info -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Kraken exchange +// GetExchangeAccountInfo : Retrieves balances for all enabled currencies for +// the Kraken exchange - TODO func (b *BTCC) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.ExchangeName = b.GetName() diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index 320e5876..b37814a6 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -13,10 +13,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the BTCE go routine func (b *BTCE) Start() { go b.Run() } +// Run implements the BTCE wrapper func (b *BTCE) Run() { if b.Verbose { log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) @@ -50,6 +52,7 @@ func (b *BTCE) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (b *BTCE) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice result, err := b.GetTicker(p.Pair().String()) @@ -72,6 +75,7 @@ func (b *BTCE) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(b.GetName(), p) if err != nil { @@ -80,12 +84,17 @@ func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tick, nil } +// GetOrderbookEx returns the orderbook for a currency pair func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { - return ob, nil + return b.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *BTCE) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := b.GetDepth(exchange.FormatExchangeCurrency(b.Name, p).String()) if err != nil { @@ -94,12 +103,12 @@ func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(ob.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(ob.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } orderBook.Pair = p @@ -107,11 +116,12 @@ func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err return orderBook, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the BTCE exchange -func (e *BTCE) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// BTCE exchange +func (b *BTCE) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetAccountInfo() + response.ExchangeName = b.GetName() + accountBalance, err := b.GetAccountInfo() if err != nil { return response, err } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 3e85669b..a06b7bcb 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -11,12 +11,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -// Start runs ticker monitor in a new routine +// Start starts the BTC Markets go routine func (b *BTCMarkets) Start() { go b.Run() } -// Run starts a go routine to monitor ticker price +// Run implements the BTC Markets wrapper func (b *BTCMarkets) Run() { if b.Verbose { log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) @@ -50,6 +50,7 @@ func (b *BTCMarkets) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(p.GetFirstCurrency().String()) @@ -63,6 +64,8 @@ func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, erro ticker.ProcessTicker(b.GetName(), p, tickerPrice) return tickerPrice, nil } + +// GetTickerPrice returns the ticker for a currency pair func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p) if err != nil { @@ -75,9 +78,13 @@ func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, er func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { - return ob, nil + return b.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := b.GetOrderbook(p.GetFirstCurrency().String()) if err != nil { diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 32621e94..a183016e 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the COINUT go routine func (c *COINUT) Start() { go c.Run() } +// Run implements the COINUT wrapper func (c *COINUT) Run() { if c.Verbose { log.Printf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket), COINUT_WEBSOCKET_URL) @@ -44,7 +46,8 @@ func (c *COINUT) Run() { } } -// GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the COINUT exchange +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// COINUT exchange func (c *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo /* @@ -65,6 +68,7 @@ func (c *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } +// UpdateTicker updates and returns the ticker for a currency pair func (c *COINUT) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.Pair().String()]) @@ -82,6 +86,7 @@ func (c *COINUT) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { } +// GetTickerPrice returns the ticker for a currency pair func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(c.GetName(), p) if err != nil { @@ -90,12 +95,17 @@ func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(c.GetName(), p) if err == nil { - return ob, nil + return c.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (c *COINUT) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.Pair().String()], 200) if err != nil { @@ -109,6 +119,7 @@ func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, e for x := range orderbookNew.Sell { orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price}) } + orderBook.Pair = p orderbook.ProcessOrderbook(c.GetName(), p, orderBook) return orderBook, nil diff --git a/exchanges/exchange.go b/exchanges/exchange.go index ef7a2358..9ebb7ce7 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -66,6 +66,7 @@ type IBotExchange interface { GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, error) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) + UpdateOrderbook(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) GetEnabledCurrencies() []pair.CurrencyPair GetExchangeAccountInfo() (AccountInfo, error) GetAuthenticatedAPISupport() bool diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 6895b669..13583d78 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the GDAX go routine func (g *GDAX) Start() { go g.Run() } +// Run implements the GDAX wrapper func (g *GDAX) Run() { if g.Verbose { log.Printf("%s Websocket: %s. (url: %s).\n", g.GetName(), common.IsEnabled(g.Websocket), GDAX_WEBSOCKET_URL) @@ -42,7 +44,8 @@ func (g *GDAX) Run() { } } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the GDAX exchange +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// GDAX exchange func (g *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.ExchangeName = g.GetName() @@ -61,6 +64,7 @@ func (g *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } +// UpdateTicker updates and returns the ticker for a currency pair func (g *GDAX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := g.GetTicker(exchange.FormatExchangeCurrency(g.Name, p).String()) @@ -83,6 +87,7 @@ func (g *GDAX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(g.GetName(), p) if err != nil { @@ -91,12 +96,17 @@ func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(g.GetName(), p) if err == nil { - return ob, nil + return g.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := g.GetOrderbook(p.Pair().String(), 2) if err != nil { @@ -112,6 +122,7 @@ func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err for x := range obNew.Asks { orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } + orderBook.Pair = p orderbook.ProcessOrderbook(g.GetName(), p, orderBook) return orderBook, nil diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 653979fe..b948ce05 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the Gemini go routine func (g *Gemini) Start() { go g.Run() } +// Run implements the Gemini wrapper func (g *Gemini) Run() { if g.Verbose { log.Printf("%s polling delay: %ds.\n", g.GetName(), g.RESTPollingDelay) @@ -31,11 +33,12 @@ func (g *Gemini) Run() { } } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Gemini exchange -func (e *Gemini) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo Retrieves balances for all enabled currencies for the +// Gemini exchange +func (g *Gemini) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetBalances() + response.ExchangeName = g.GetName() + accountBalance, err := g.GetBalances() if err != nil { return response, err } @@ -49,6 +52,7 @@ func (e *Gemini) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } +// UpdateTicker updates and returns the ticker for a currency pair func (g *Gemini) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := g.GetTicker(p.Pair().String()) @@ -64,6 +68,7 @@ func (g *Gemini) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(g.GetName(), p) if err != nil { @@ -72,12 +77,17 @@ func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(g.GetName(), p) if err == nil { - return ob, nil + return g.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (g *Gemini) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := g.GetOrderbook(p.Pair().String(), url.Values{}) if err != nil { diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 09432302..07d36619 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the HUOBI go routine func (h *HUOBI) Start() { go h.Run() } +// Run implements the HUOBI wrapper func (h *HUOBI) Run() { if h.Verbose { log.Printf("%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket), HUOBI_SOCKETIO_ADDRESS) @@ -26,6 +28,7 @@ func (h *HUOBI) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (h *HUOBI) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := h.GetTicker(p.GetFirstCurrency().Lower().String()) @@ -43,6 +46,7 @@ func (h *HUOBI) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(h.GetName(), p) if err != nil { @@ -51,12 +55,17 @@ func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(h.GetName(), p) if err == nil { - return ob, nil + return h.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := h.GetOrderBook(p.GetFirstCurrency().Lower().String()) if err != nil { @@ -72,15 +81,16 @@ func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } + orderBook.Pair = p orderbook.ProcessOrderbook(h.GetName(), p, orderBook) return orderBook, nil } -//TODO: retrieve HUOBI balance info -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the HUOBI exchange -func (e *HUOBI) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +//GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// HUOBI exchange - to-do +func (h *HUOBI) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() + response.ExchangeName = h.GetName() return response, nil } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 702a75e2..445abc61 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -10,9 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the ItBit go routine func (i *ItBit) Start() { go i.Run() } + +// Run implements the ItBit wrapper func (i *ItBit) Run() { if i.Verbose { log.Printf("%s polling delay: %ds.\n", i.GetName(), i.RESTPollingDelay) @@ -20,6 +23,7 @@ func (i *ItBit) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (i *ItBit) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := i.GetTicker(p.Pair().String()) @@ -38,6 +42,7 @@ func (i *ItBit) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(i.GetName(), p) if err != nil { @@ -46,12 +51,17 @@ func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(i.GetName(), p) if err == nil { - return ob, nil + return i.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := i.GetOrderbook(p.Pair().String()) if err != nil { @@ -83,13 +93,14 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er } orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: amount, Price: price}) } + orderBook.Pair = p orderbook.ProcessOrderbook(i.GetName(), p, orderBook) return orderBook, nil } -//TODO Get current holdings from ItBit -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the ItBit exchange +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +//ItBit exchange - to-do func (i *ItBit) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.ExchangeName = i.GetName() diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index ee99b2b4..cb90144e 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -187,20 +187,62 @@ func (k *Kraken) GetOHLC(symbol string) error { return nil } -func (k *Kraken) GetDepth(symbol string) error { +// GetDepth returns the orderbook for a particular currency +func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { values := url.Values{} values.Set("pair", symbol) var result interface{} + var ob Orderbook path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_DEPTH, values.Encode()) err := common.SendHTTPGetRequest(path, true, &result) if err != nil { - return err + return ob, err } - log.Println(result) - return nil + data := result.(map[string]interface{}) + orderbookData := data["result"].(map[string]interface{}) + + var bidsData []interface{} + var asksData []interface{} + for _, y := range orderbookData { + yData := y.(map[string]interface{}) + bidsData = yData["bids"].([]interface{}) + asksData = yData["asks"].([]interface{}) + } + + processOrderbook := func(data []interface{}) ([]OrderbookBase, error) { + var result []OrderbookBase + for x := range data { + entry := data[x].([]interface{}) + + price, err := strconv.ParseFloat(entry[0].(string), 64) + if err != nil { + return nil, err + } + + amount, err := strconv.ParseFloat(entry[1].(string), 64) + if err != nil { + return nil, err + } + + result = append(result, OrderbookBase{Price: price, Amount: amount}) + } + return result, nil + } + + ob.Bids, err = processOrderbook(bidsData) + if err != nil { + return ob, err + } + + ob.Asks, err = processOrderbook(asksData) + if err != nil { + return ob, err + } + + return ob, nil } func (k *Kraken) GetTrades(symbol string) error { diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index f1ec460d..007009fc 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -31,6 +31,18 @@ type KrakenTicker struct { Open float64 } +// OrderbookBase stores the orderbook price and amount data +type OrderbookBase struct { + Price float64 + Amount float64 +} + +// Orderbook stores the bids and asks orderbook data +type Orderbook struct { + Bids []OrderbookBase + Asks []OrderbookBase +} + type KrakenTickerResponse struct { Ask []string `json:"a"` Bid []string `json:"b"` diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 3b27b576..c05e69c8 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -9,10 +9,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the Kraken go routine func (k *Kraken) Start() { go k.Run() } +// Run implements the Kraken wrapper func (k *Kraken) Run() { if k.Verbose { log.Printf("%s polling delay: %ds.\n", k.GetName(), k.RESTPollingDelay) @@ -34,9 +36,9 @@ func (k *Kraken) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice - pairs := k.GetEnabledCurrencies() pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs) if err != nil { @@ -66,7 +68,7 @@ func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return ticker.GetTicker(k.GetName(), p) } -//This will return the TickerPrice struct when tickers are completed here.. +// GetTickerPrice returns the ticker for a currency pair func (k *Kraken) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(k.GetName(), p) if err != nil { @@ -75,14 +77,40 @@ func (k *Kraken) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (k *Kraken) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - return orderbook.OrderbookBase{}, nil + ob, err := orderbook.GetOrderbook(k.GetName(), p) + if err == nil { + return k.UpdateOrderbook(p) + } + return ob, nil } -//TODO: Retrieve Kraken info -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Kraken exchange -func (e *Kraken) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (k *Kraken) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { + var orderBook orderbook.OrderbookBase + orderbookNew, err := k.GetDepth(exchange.FormatExchangeCurrency(k.GetName(), p).String()) + if err != nil { + return orderBook, err + } + + for x := range orderbookNew.Bids { + orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) + } + + for x := range orderbookNew.Asks { + orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) + } + + orderBook.Pair = p + orderbook.ProcessOrderbook(k.GetName(), p, orderBook) + return orderBook, nil +} + +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// Kraken exchange - to-do +func (k *Kraken) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() + response.ExchangeName = k.GetName() return response, nil } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 12bb72c9..e9b463ab 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -11,9 +11,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the LakeBTC go routine func (l *LakeBTC) Start() { go l.Run() } + +// Run implements the LakeBTC wrapper func (l *LakeBTC) Run() { if l.Verbose { log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) @@ -21,6 +24,7 @@ func (l *LakeBTC) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := l.GetTicker() if err != nil { @@ -44,6 +48,7 @@ func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p) if err != nil { @@ -52,12 +57,17 @@ func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(l.GetName(), p) if err == nil { - return ob, nil + return l.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (l *LakeBTC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := l.GetOrderBook(p.Pair().String()) if err != nil { @@ -77,6 +87,8 @@ func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, return orderBook, nil } +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// LakeBTC exchange func (l *LakeBTC) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.ExchangeName = l.GetName() diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 74cd9d43..ae3d5b79 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the Liqui go routine func (l *Liqui) Start() { go l.Run() } +// Run implements the Liqui wrapper func (l *Liqui) Run() { if l.Verbose { log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) @@ -33,6 +35,7 @@ func (l *Liqui) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (l *Liqui) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice pairsString, err := exchange.GetAndFormatExchangeCurrencies(l.Name, @@ -62,6 +65,7 @@ func (l *Liqui) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return ticker.GetTicker(l.GetName(), p) } +// GetTickerPrice returns the ticker for a currency pair func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p) if err != nil { @@ -70,12 +74,17 @@ func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(l.GetName(), p) if err == nil { - return ob, nil + return l.UpdateOrderbook(p) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := l.GetDepth(exchange.FormatExchangeCurrency(l.Name, p).String()) if err != nil { @@ -91,16 +100,18 @@ func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } + orderBook.Pair = p orderbook.ProcessOrderbook(l.GetName(), p, orderBook) return orderBook, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Liqui exchange -func (e *Liqui) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// Liqui exchange +func (l *Liqui) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetAccountInfo() + response.ExchangeName = l.GetName() + accountBalance, err := l.GetAccountInfo() if err != nil { return response, err } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 3712389d..5a980492 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -11,10 +11,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the LocalBitcoins go routine func (l *LocalBitcoins) Start() { go l.Run() } +// Run implements the LocalBitcoins wrapper func (l *LocalBitcoins) Run() { if l.Verbose { log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay) @@ -22,6 +24,7 @@ func (l *LocalBitcoins) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := l.GetTicker() @@ -41,6 +44,7 @@ func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, e return ticker.GetTicker(l.GetName(), p) } +// GetTickerPrice returns the ticker for a currency pair func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(l.GetName(), p) if err == nil { @@ -49,15 +53,44 @@ func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (l *LocalBitcoins) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - return orderbook.OrderbookBase{}, nil + ob, err := orderbook.GetOrderbook(l.GetName(), p) + if err == nil { + return l.UpdateOrderbook(p) + } + return ob, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the LocalBitcoins exchange -func (e *LocalBitcoins) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { + var orderBook orderbook.OrderbookBase + orderbookNew, err := l.GetOrderbook(p.GetSecondCurrency().String()) + if err != nil { + return orderBook, err + } + + for x := range orderbookNew.Bids { + data := orderbookNew.Bids[x] + orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + } + + for x := range orderbookNew.Asks { + data := orderbookNew.Asks[x] + orderBook.Bids = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + } + + orderBook.Pair = p + orderbook.ProcessOrderbook(l.GetName(), p, orderBook) + return orderBook, nil +} + +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// LocalBitcoins exchange +func (l *LocalBitcoins) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetWalletBalance() + response.ExchangeName = l.GetName() + accountBalance, err := l.GetWalletBalance() if err != nil { return response, err } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index ae05ffde..937ed9aa 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -12,10 +12,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the OKCoin go routine func (o *OKCoin) Start() { go o.Run() } +// Run implements the OKCoin wrapper func (o *OKCoin) Run() { if o.Verbose { log.Printf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket), o.WebsocketURL) @@ -50,6 +52,7 @@ func (o *OKCoin) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (o *OKCoin) UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := o.GetTicker(exchange.FormatExchangeCurrency(o.Name, currency).String()) @@ -67,6 +70,7 @@ func (o *OKCoin) UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, e return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(o.GetName(), currency) if err != nil { @@ -75,12 +79,17 @@ func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(o.GetName(), currency) if err == nil { - return ob, nil + return o.UpdateOrderbook(currency) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase orderbookNew, err := o.GetOrderBook(exchange.FormatExchangeCurrency(o.Name, currency).String(), 200, false) if err != nil { @@ -96,15 +105,18 @@ func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.Orderbook data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } + orderBook.Pair = currency orderbook.ProcessOrderbook(o.GetName(), currency, orderBook) return orderBook, nil } -func (e *OKCoin) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// OKCoin exchange +func (o *OKCoin) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - assets, err := e.GetUserInfo() + response.ExchangeName = o.GetName() + assets, err := o.GetUserInfo() if err != nil { return response, err } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 1e308de0..e779f4b8 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -10,10 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts the Poloniex go routine func (p *Poloniex) Start() { go p.Run() } +// Run implements the Poloniex wrapper func (p *Poloniex) Run() { if p.Verbose { log.Printf("%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket), POLONIEX_WEBSOCKET_ADDRESS) @@ -26,8 +28,9 @@ func (p *Poloniex) Run() { } } +// UpdateTicker updates and returns the ticker for a currency pair func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { - currency := currencyPair.Pair().String() + currency := exchange.FormatCurrency(currencyPair).String() var tickerPrice ticker.TickerPrice tick, err := p.GetTicker() if err != nil { @@ -45,6 +48,7 @@ func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair) (ticker.TickerPr return tickerPrice, nil } +// GetTickerPrice returns the ticker for a currency pair func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair) if err != nil { @@ -53,15 +57,19 @@ func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.Ticker return tickerNew, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) { - currency := currencyPair.Pair().String() ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair) if err == nil { - return ob, nil + return p.UpdateOrderbook(currencyPair) } + return ob, nil +} +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase - orderbookNew, err := p.GetOrderbook(currency, 1000) + orderbookNew, err := p.GetOrderbook(exchange.FormatCurrency(currencyPair).String(), 1000) if err != nil { return orderBook, err } @@ -80,11 +88,12 @@ func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.Ord return orderBook, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Poloniex exchange -func (e *Poloniex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// Poloniex exchange +func (p *Poloniex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetBalances() + response.ExchangeName = p.GetName() + accountBalance, err := p.GetBalances() if err != nil { return response, err } diff --git a/main.go b/main.go index 5a20e936..08a6b9d8 100644 --- a/main.go +++ b/main.go @@ -191,6 +191,7 @@ func main() { go WebsocketHandler() go TickerUpdaterRoutine() + go OrderbookUpdaterRoutine() if bot.config.Webserver.Enabled { err := bot.config.CheckWebserverConfigValues() diff --git a/routines.go b/routines.go index 52afd11a..51cbd657 100644 --- a/routines.go +++ b/routines.go @@ -19,7 +19,7 @@ func TickerUpdaterRoutine() { currency := enabledCurrencies[y] result, err := bot.exchanges[x].UpdateTicker(currency) if err != nil { - log.Printf("failed to get %s currency", currency.Pair().String()) + log.Printf("failed to get %s ticker", currency.Pair().String()) continue } @@ -45,3 +45,37 @@ func TickerUpdaterRoutine() { time.Sleep(time.Second * 10) } } + +func OrderbookUpdaterRoutine() { + log.Println("Starting orderbook updater routine") + for { + for x := range bot.exchanges { + if bot.exchanges[x].IsEnabled() { + exchangeName := bot.exchanges[x].GetName() + enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() + + for y := range enabledCurrencies { + currency := enabledCurrencies[y] + result, err := bot.exchanges[x].UpdateOrderbook(currency) + if err != nil { + log.Printf("failed to get %s orderbook", currency.Pair().String()) + continue + } + + log.Printf("%s %s %v", + exchangeName, + exchange.FormatCurrency(currency).String(), + result) + + evt := WebsocketEvent{ + Data: result, + Event: "orderbook_update", + Exchange: exchangeName, + } + BroadcastWebsocketMessage(evt) + } + } + } + time.Sleep(time.Second * 10) + } +} From 913c104d09993a0313c1fca02c7d663d94173cbe Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 31 Aug 2017 15:39:20 +1000 Subject: [PATCH 14/32] Finish off websocket ticker demo, update config files and fix tests --- config/config.go | 1 + config_example.dat | 25 +++- events/event_test.go | 28 ++--- events/events.go | 10 +- exchanges/alphapoint/alphapoint.go | 2 + exchanges/alphapoint/alphapoint_wrapper.go | 15 ++- exchanges/anx/anx.go | 6 + exchanges/anx/anx_wrapper.go | 35 ++---- exchanges/bitfinex/bitfinex.go | 6 + exchanges/bitfinex/bitfinex_wrapper.go | 17 ++- exchanges/bitfinex/bitfinex_wrapper_test.go | 3 +- exchanges/bitstamp/bitstamp.go | 6 + exchanges/bitstamp/bitstamp_wrapper.go | 17 ++- exchanges/bittrex/bittrex.go | 6 + exchanges/bittrex/bittrex_wrapper.go | 17 ++- exchanges/btcc/btcc.go | 6 + exchanges/btcc/btcc_wrapper.go | 17 ++- exchanges/btce/btce.go | 6 + exchanges/btce/btce_wrapper.go | 17 ++- exchanges/btcmarkets/btcmarkets.go | 6 + exchanges/btcmarkets/btcmarkets_wrapper.go | 17 ++- exchanges/coinut/coinut.go | 6 + exchanges/coinut/coinut_types.go | 2 +- exchanges/coinut/coinut_wrapper.go | 19 ++-- exchanges/exchange.go | 41 ++++++- exchanges/gdax/gdax.go | 6 + exchanges/gdax/gdax_wrapper.go | 23 ++-- exchanges/gemini/gemini.go | 6 + exchanges/gemini/gemini_wrapper.go | 17 ++- exchanges/huobi/huobi.go | 6 + exchanges/huobi/huobi_wrapper.go | 17 ++- exchanges/itbit/itbit.go | 6 + exchanges/itbit/itbit_wrapper.go | 23 ++-- exchanges/kraken/kraken.go | 6 + exchanges/kraken/kraken_wrapper.go | 19 ++-- exchanges/lakebtc/lakebtc.go | 6 + exchanges/lakebtc/lakebtc_wrapper.go | 39 +++---- exchanges/liqui/liqui.go | 6 + exchanges/liqui/liqui_wrapper.go | 41 ++++--- exchanges/localbitcoins/localbitcoins.go | 6 + .../localbitcoins/localbitcoins_wrapper.go | 33 +++--- exchanges/okcoin/okcoin.go | 7 ++ exchanges/okcoin/okcoin_wrapper.go | 79 ++++++------- exchanges/orderbook/orderbook.go | 1 + exchanges/poloniex/poloniex.go | 16 ++- exchanges/poloniex/poloniex_wrapper.go | 39 ++++--- exchanges/ticker/ticker.go | 107 +++++++++++------- exchanges/ticker/ticker_test.go | 77 +++++++------ main.go | 2 +- routines.go | 85 ++++++++++---- testdata/configtest.dat | 22 +++- ticker_routes.go | 49 ++++++-- websocket.go | 14 ++- 53 files changed, 677 insertions(+), 412 deletions(-) diff --git a/config/config.go b/config/config.go index 531d3127..322a13cd 100644 --- a/config/config.go +++ b/config/config.go @@ -110,6 +110,7 @@ type ExchangeConfig struct { AvailablePairs string EnabledPairs string BaseCurrencies string + AssetTypes string ConfigCurrencyPairFormat *CurrencyPairFormatConfig `json:"ConfigCurrencyPairFormat"` RequestCurrencyPairFormat *CurrencyPairFormatConfig `json:"RequestCurrencyPairFormat"` } diff --git a/config_example.dat b/config_example.dat index 4e131c0f..ff0a64e2 100644 --- a/config_example.dat +++ b/config_example.dat @@ -65,6 +65,7 @@ "AvailablePairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", "EnabledPairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Index": "BTC" @@ -86,6 +87,7 @@ "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BCCBTC,BCUBTC,BCCUSD,BCUUSD,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH", "EnabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -106,6 +108,7 @@ "AvailablePairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "EnabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "BaseCurrencies": "USD,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -122,9 +125,10 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-NAUT,BTC-VRC,BTC-CURE,BTC-XBB,BTC-XMR,BTC-CLOAK,BTC-START,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-BTCD,BTC-VIA,BTC-UNO,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BLITZ,BTC-BAY,BTC-BTS,BTC-FAIR,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-SNRG,BTC-PKB,BTC-CPC,BTC-AEON,BTC-ETH,BTC-GCR,BTC-TX,BTC-BCY,BTC-EXP,BTC-INFX,BTC-OMNI,BTC-AMP,BTC-AGRS,BTC-XLM,BTC-BTA,USDT-BTC,BITCNY-BTC,BTC-CLUB,BTC-VOX,BTC-EMC,BTC-FCT,BTC-MAID,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-SAFEX,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-XVC,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-PDC,BTC-BRK,BTC-DGD,ETH-DGD,BTC-WAVES,BTC-RISE,BTC-LBC,BTC-SBD,BTC-BRX,BTC-DRACO,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-TRIG,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-XAUR,BTC-SNGLS,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-DAR,BTC-GOLOS,BTC-HKG,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-TIME,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-SNGLS,ETH-GNO,BTC-APX,BTC-TKN,ETH-TKN,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,ETH-1ST,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-MYST,ETH-MYST,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-TIME,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-FUN,ETH-FUN,BTC-PAY,ETH-PAY,BTC-MTL,ETH-MTL,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,ETH-BTS", + "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-VRC,BTC-CURE,BTC-XBB,BTC-XMR,BTC-CLOAK,BTC-START,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-BTCD,BTC-VIA,BTC-UNO,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BLITZ,BTC-BAY,BTC-BTS,BTC-FAIR,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-SNRG,BTC-PKB,BTC-CPC,BTC-AEON,BTC-ETH,BTC-GCR,BTC-TX,BTC-BCY,BTC-EXP,BTC-INFX,BTC-OMNI,BTC-AMP,BTC-AGRS,BTC-XLM,BTC-BTA,USDT-BTC,BTC-CLUB,BTC-VOX,BTC-EMC,BTC-FCT,BTC-MAID,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-SAFEX,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-XVC,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-PDC,BTC-BRK,BTC-DGD,ETH-DGD,BTC-WAVES,BTC-RISE,BTC-LBC,BTC-SBD,BTC-BRX,BTC-DRACO,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-TRIG,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-XAUR,BTC-SNGLS,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-DAR,BTC-GOLOS,BTC-HKG,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-TIME,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-SNGLS,ETH-GNO,BTC-APX,BTC-TKN,ETH-TKN,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,ETH-1ST,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-MYST,ETH-MYST,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-TIME,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-FUN,ETH-FUN,BTC-PAY,ETH-PAY,BTC-MTL,ETH-MTL,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,ETH-BTS", "EnabledPairs": "USDT-BTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Delimiter": "-" @@ -146,6 +150,7 @@ "AvailablePairs": "BTCCNY,LTCCNY,LTCBTC", "EnabledPairs": "BTCCNY,LTCCNY,LTCBTC", "BaseCurrencies": "CNY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -165,6 +170,7 @@ "AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", "EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", "BaseCurrencies": "USD,RUR,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -186,6 +192,7 @@ "AvailablePairs": "LTCAUD,BTCAUD", "EnabledPairs": "LTCAUD,BTCAUD", "BaseCurrencies": "AUD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -206,6 +213,7 @@ "AvailablePairs": "LTCBTC,ETCBTC,ETHBTC", "EnabledPairs": "LTCBTC,ETCBTC,ETHBTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -226,6 +234,7 @@ "AvailablePairs": "LTCEUR,LTCBTC,BTCGBP,BTCEUR,ETHEUR,ETHBTC,LTCUSD,BTCUSD,ETHUSD", "EnabledPairs": "BTCUSD,BTCGBP,BTCEUR", "BaseCurrencies": "USD,GBP,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -246,6 +255,7 @@ "AvailablePairs": "BTCUSD,ETHBTC,ETHUSD", "EnabledPairs": "BTCUSD", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -265,6 +275,7 @@ "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", "BaseCurrencies": "CNY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -285,6 +296,7 @@ "AvailablePairs": "XBTUSD,XBTSGD,XBTEUR", "EnabledPairs": "XBTUSD,XBTSGD,XBTEUR", "BaseCurrencies": "USD,SGD,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -301,9 +313,10 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "XBTUSD,ZECEUR,REPETH,XBTJPY,ETHEUR.D,LTCXBT,GNOXBT,ETHCAD.D,ETHEUR,ETHUSD,ICNXBT,XDGXBT,BCHUSD,DASHEUR,ETHJPY.D,MLNXBT,XBTCAD,GNOETH,ETCUSD,REPXBT,ETCXBT,ICNETH,ETHXBT,XBTJPY.D,XMREUR,XRPUSD,ZECXBT,ETHCAD,XBTGBP.D,MLNETH,BCHEUR,ETCEUR,XBTEUR,XLMXBT,XRPXBT,ETCETH,REPEUR,XMRUSD,ZECUSD,USDTUSD,ETHXBT.D,ETHJPY,ETHUSD.D,XBTUSD.D,LTCUSD,XBTCAD.D,BCHXBT,DASHUSD,EOSXBT,ETHGBP.D,XMRXBT,XRPEUR,DASHXBT,EOSETH,LTCEUR,XBTEUR.D", + "AvailablePairs": "BCHEUR,REPEUR,XBTGBP,XBTUSD,ETHXBT,MLNXBT,ETCEUR,ETHGBP,ICNXBT,ZECEUR,EOSETH,GNOXBT,ETHCAD.D,ETHGBP.D,XRPEUR,BCHXBT,EOSXBT,LTCXBT,XBTEUR.D,XBTUSD.D,DASHUSD,GNOETH,ETHJPY,ETHUSD.D,REPETH,USDTUSD,ETHEUR,XLMXBT,BCHUSD,ETHCAD,XBTEUR,XMRUSD,ZECXBT,LTCUSD,XBTCAD,XMRXBT,ETHJPY.D,ICNETH,XBTCAD.D,XBTJPY,XRPUSD,ZECUSD,DASHEUR,ETCETH,ETCUSD,MLNETH,XMREUR,DASHXBT,ETHXBT.D,XDGXBT,XBTGBP.D,XRPXBT,XBTJPY.D,ETCXBT,ETHEUR.D,ETHUSD,LTCEUR,REPXBT", "EnabledPairs": "ETCUSD,XBTUSD,ETHUSD", "BaseCurrencies": "EUR,USD,CAD,GBP,JPY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -324,6 +337,7 @@ "AvailablePairs": "BTCUSD,BTCEUR,USDHKD,AUDUSD,BTCGBP,BTCNZD,USDJPY,BTCSGD,BTCNGN,EURUSD,USDSGD,NZDUSD,USDNGN,USDCHF,BTCJPY,BTCAUD,BTCCAD,BTCCHF,GBPUSD,USDCAD", "EnabledPairs": "BTCUSD,BTCAUD", "BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -340,9 +354,10 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "SAN_BTC,OAX_BTC,VSL_BTC,PLU_USDT,GUP_ETH,SNT_ETH,EOS_ETH,ICN_USDT,CVC_USDT,DASH_BTC,DASH_USDT,WINGS_USDT,LUN_ETH,CFI_USDT,OAX_USDT,BCAP_ETH,MCO_BTC,STORJ_BTC,ICN_BTC,LTC_ETH,TAAS_BTC,BNT_ETH,QTUM_ETH,REP_ETH,RLC_BTC,HMQ_ETH,TIME_ETH,QRL_USDT,PTOY_USDT,LTC_BTC,GNT_BTC,RLC_USDT,SNT_BTC,RLC_ETH,TRST_USDT,MCO_ETH,ADX_BTC,VSL_USDT,TRST_ETH,DGD_USDT,BCC_ETH,SNM_ETH,DNT_ETH,GNT_ETH,TAAS_USDT,HMQ_USDT,BAT_ETH,STORJ_ETH,ADX_ETH,OMG_USDT,TIME_BTC,PLU_ETH,WINGS_ETH,SNGLS_BTC,CFI_ETH,SAN_ETH,DNT_USDT,STX_ETH,WAVES_BTC,1ST_ETH,INCNT_ETH,MYST_USDT,PTOY_ETH,MLN_USDT,QRL_BTC,ADX_USDT,PAY_ETH,STX_BTC,QTUM_BTC,CVC_ETH,STX_USDT,MLN_BTC,ICN_ETH,BTC_USDT,TRST_BTC,SNM_BTC,NET_BTC,CVC_BTC,OAX_ETH,1ST_BTC,GNT_USDT,GUP_BTC,BAT_USDT,BNT_USDT,STORJ_USDT,PLU_BTC,DASH_ETH,BCAP_USDT,QRL_ETH,PTOY_BTC,PAY_BTC,ZRX_ETH,ZRX_USDT,LTC_USDT,GNO_BTC,TKN_BTC,HMQ_BTC,MCO_USDT,GUP_USDT,BCC_BTC,XID_BTC,ETH_USDT,INCNT_USDT,GNO_USDT,CFI_BTC,WAVES_USDT,QTUM_USDT,NET_USDT,DNT_BTC,ROUND_ETH,REP_BTC,TKN_USDT,XID_USDT,DGD_ETH,MYST_ETH,SNT_USDT,PAY_USDT,BCC_USDT,ROUND_BTC,ANT_ETH,OMG_ETH,NET_ETH,DGD_BTC,SAN_USDT,WINGS_BTC,VSL_ETH,ROUND_USDT,LUN_BTC,LUN_USDT,EDG_USDT,ANT_USDT,EOS_USDT,ETH_BTC,INCNT_BTC,WAVES_ETH,TIME_USDT,EDG_BTC,XID_ETH,SNGLS_USDT,SNM_USDT,OMG_BTC,GNO_ETH,MGO_ETH,MGO_USDT,MYST_BTC,ZRX_BTC,BNT_BTC,MGO_BTC,SNGLS_ETH,1ST_USDT,EDG_ETH,REP_USDT,BCAP_BTC,ANT_BTC,MLN_ETH,TAAS_ETH,TKN_ETH,BAT_BTC,EOS_BTC", + "AvailablePairs": "LUN_BTC,BCAP_ETH,NET_USDT,WAVES_ETH,GNO_ETH,CVC_ETH,GNO_BTC,XID_BTC,TAAS_BTC,MGO_ETH,STORJ_BTC,ADX_USDT,BCC_BTC,ICN_ETH,ETH_USDT,LUN_ETH,SNGLS_BTC,OMG_USDT,STX_BTC,RLC_USDT,TRST_BTC,STX_USDT,INCNT_ETH,EOS_BTC,CVC_USDT,NET_ETH,DGD_BTC,OAX_ETH,DNT_ETH,DASH_USDT,QTUM_BTC,TKN_USDT,SNM_USDT,MCO_ETH,SAN_ETH,TNT_ETH,ROUND_BTC,VSL_ETH,SAN_USDT,VSL_BTC,INCNT_BTC,STORJ_ETH,ZRX_ETH,BCAP_BTC,PTOY_ETH,PAY_BTC,MGO_USDT,EOS_USDT,TIME_USDT,INCNT_USDT,ANT_BTC,MYST_ETH,CFI_ETH,SNM_BTC,DASH_BTC,MLN_BTC,OMG_BTC,SAN_BTC,QTUM_ETH,LTC_ETH,QRL_ETH,QRL_USDT,BNT_ETH,QTUM_USDT,WAVES_USDT,REP_ETH,BNT_BTC,ETH_BTC,WINGS_USDT,SNGLS_ETH,XID_USDT,TNT_BTC,GNT_ETH,WINGS_ETH,BTC_USDT,GUP_USDT,TAAS_ETH,LUN_USDT,HMQ_ETH,MYST_BTC,WAVES_BTC,MLN_ETH,TNT_USDT,STORJ_USDT,OMG_ETH,EDG_BTC,GNO_USDT,BAT_ETH,SNT_USDT,DNT_BTC,PLU_ETH,REP_BTC,ADX_BTC,PAY_ETH,DGD_USDT,ZRX_BTC,WINGS_BTC,QRL_BTC,MCO_BTC,VSL_USDT,BAT_BTC,ANT_USDT,PAY_USDT,XID_ETH,TKN_BTC,EOS_ETH,NET_BTC,RLC_BTC,PTOY_BTC,SNM_ETH,OAX_BTC,1ST_ETH,BCAP_USDT,TRST_USDT,PLU_USDT,GUP_ETH,MCO_USDT,BCC_ETH,ROUND_ETH,TIME_ETH,TIME_BTC,ICN_USDT,GUP_BTC,SNGLS_USDT,PLU_BTC,MYST_USDT,CFI_USDT,SNT_BTC,SNT_ETH,ZRX_USDT,ICN_BTC,BAT_USDT,REP_USDT,HMQ_BTC,OAX_USDT,LTC_BTC,EDG_ETH,GNT_USDT,ROUND_USDT,BNT_USDT,CFI_BTC,CVC_BTC,BCC_USDT,GNT_BTC,STX_ETH,1ST_BTC,MGO_BTC,DNT_USDT,DASH_ETH,1ST_USDT,EDG_USDT,TKN_ETH,PTOY_USDT,ADX_ETH,LTC_USDT,RLC_ETH,HMQ_USDT,ANT_ETH,DGD_ETH,MLN_USDT,TRST_ETH,TAAS_USDT", "EnabledPairs": "ETH_BTC,LTC_BTC,DASH_BTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Delimiter": "_" @@ -365,6 +380,7 @@ "AvailablePairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", "EnabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -384,6 +400,7 @@ "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", "BaseCurrencies": "CNY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -404,6 +421,7 @@ "AvailablePairs": "BTCUSD,LTCUSD", "EnabledPairs": "BTCUSD,LTCUSD", "BaseCurrencies": "USD", + "AssetTypes": "SPOT,this_week,next_week,quarter", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -424,6 +442,7 @@ "AvailablePairs": "BTC_XUSD,BTC_FCT,BTC_MMNXT,BTC_NMC,BTC_BITUSD,BTC_RDD,BTC_XMR,BTC_XST,BTC_DSH,BTC_MAID,BTC_DGB,BTC_NEOS,BTC_BLK,BTC_NAUT,BTC_NBT,BTC_XCP,BTC_STR,BTC_BTCD,BTC_GRC,BTC_HUC,BTC_BBR,BTC_XDN,BTC_INDEX,BTC_IOC,BTC_SWARM,BTC_EMC2,BTC_MCN,BTC_NOXT,BTC_MINT,BTC_PTS,BTC_SC,BTC_GEO,BTC_XRP,BTC_FLO,BTC_BITS,BTC_HYP,BTC_XCR,BTC_LTBC,BTC_SYS,BTC_GMC,BTC_ETH,BTC_SYNC,BTC_GAP,BTC_BCN,BTC_C2,BTC_PINK,BTC_FIBRE,BTC_POT,BTC_QTL,BTC_SDC,BTC_XC,BTC_DASH,BTC_SILK,BTC_CLAM,BTC_NAV,BTC_PIGGY,BTC_BCY,BTC_MIL,BTC_XCN,BTC_YACC,BTC_BTS,BTC_QBK,BTC_SJCX,BTC_LQD,BTC_BURST,BTC_RIC,BTC_VRC,BTC_LTC,BTC_XPB,BTC_GRS,BTC_XCH,BTC_ARCH,BTC_QORA,BTC_HZ,BTC_NSR,BTC_XPM,BTC_BITCNY,BTC_EXE,BTC_XMG,BTC_BTC,BTC_BTM,BTC_NOBL,BTC_NXT,BTC_DOGE,BTC_CURE,BTC_MNTA,BTC_ADN,BTC_EXP,BTC_VTC,BTC_FLDC,BTC_MRS,BTC_MYR,BTC_OMNI,BTC_VNL,BTC_USDT,BTC_NOTE,BTC_WDC,BTC_BELA,BTC_VIA,BTC_CGA,BTC_DIEM,BTC_IFC,BTC_XDP,BTC_BLOCK,BTC_MMC,BTC_1CR,BTC_UNITY,BTC_XBC,BTC_GEMZ,BTC_FLT,BTC_PPC,BTC_XEM,BTC_RBY,BTC_CNMT,BTC_ABY,XMR_XDN,XMR_IFC,XMR_DIEM,XMR_BBR,XMR_DSH,XMR_BCN,XMR_LTC,XMR_MAID,XMR_DASH,XMR_BTCD,XMR_HYP,XMR_BLK,XMR_QORA,XMR_MNTA,XMR_NXT,USDT_BTC,USDT_ETH,USDT_XRP,USDT_DASH,USDT_LTC,USDT_NXT,USDT_XMR,USDT_STR", "EnabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Delimiter": "_" diff --git a/events/event_test.go b/events/event_test.go index e9b9070b..c33c37bd 100644 --- a/events/event_test.go +++ b/events/event_test.go @@ -5,27 +5,27 @@ import ( ) func TestAddEvent(t *testing.T) { - eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil && eventID != 0 { t.Errorf("Test Failed. AddEvent: Error, %s", err) } - eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", actionTest) + eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Exchange") } - eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", actionTest) + eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Item") } - eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", actionTest) + eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Condition") } - eventID, err = AddEvent("ANX", "price", ">,==", "BTC", "LTC", "console_prints") + eventID, err = AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", "console_prints") if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Action") } - eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", actionTest) + eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Action") } @@ -35,7 +35,7 @@ func TestAddEvent(t *testing.T) { } func TestRemoveEvent(t *testing.T) { - eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil && eventID != 0 { t.Errorf("Test Failed. RemoveEvent: Error, %s", err) } @@ -48,15 +48,15 @@ func TestRemoveEvent(t *testing.T) { } func TestGetEventCounter(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } - two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } - three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } @@ -84,7 +84,7 @@ func TestGetEventCounter(t *testing.T) { } func TestExecuteAction(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. ExecuteAction: Error, %s", err) } @@ -100,13 +100,13 @@ func TestExecuteAction(t *testing.T) { } func TestEventToString(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. EventToString: Error, %s", err) } eventString := Events[one].EventToString() - if eventString != "If the BTCLTC price on ANX is > == then ACTION_TEST." { + if eventString != "If the BTCLTC [SPOT] price on ANX is > == then ACTION_TEST." { t.Error("Test Failed. EventToString: Error, incorrect return string") } @@ -116,7 +116,7 @@ func TestEventToString(t *testing.T) { } func TestCheckCondition(t *testing.T) { //error handling needs to be implemented - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. EventToString: Error, %s", err) } diff --git a/events/events.go b/events/events.go index 5f3aa13e..9105ec06 100644 --- a/events/events.go +++ b/events/events.go @@ -42,6 +42,7 @@ type Event struct { Item string Condition string FirstCurrency string + Asset string SecondCurrency string Action string Executed bool @@ -53,7 +54,7 @@ var Events []*Event // AddEvent adds an event to the Events chain and returns an index/eventID // and an error -func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action string) (int, error) { +func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Asset, Action string) (int, error) { err := IsValidEvent(Exchange, Item, Condition, Action) if err != nil { return 0, err @@ -76,6 +77,7 @@ func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action s Event.Condition = Condition Event.FirstCurrency = FirstCurrency Event.SecondCurrency = SecondCurrency + Event.Asset = Asset Event.Action = Action Event.Executed = false Events = append(Events, Event) @@ -131,8 +133,8 @@ func (e *Event) ExecuteAction() bool { func (e *Event) EventToString() string { condition := common.SplitStrings(e.Condition, ",") return fmt.Sprintf( - "If the %s%s %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency, - e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action, + "If the %s%s [%s] %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency, + e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action, ) } @@ -147,7 +149,7 @@ func (e *Event) CheckCondition() bool { return false } - lastPrice := ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)].Last + lastPrice := ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)][e.Asset].Last if lastPrice == 0 { return false diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 8da73698..327bc2dd 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -49,6 +50,7 @@ type Alphapoint struct { func (a *Alphapoint) SetDefaults() { a.APIUrl = alphapointDefaultAPIURL a.WebsocketURL = alphapointDefaultWebsocketURL + a.AssetTypes = []string{ticker.Spot} } // GetTicker returns current ticker information from Alphapoint for a selected diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 8ec18e8d..b93b0968 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -29,8 +29,8 @@ func (a *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := a.GetTicker(p.Pair().String()) if err != nil { return tickerPrice, err @@ -43,13 +43,13 @@ func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, erro tickerPrice.High = tick.High tickerPrice.Volume = tick.Volume tickerPrice.Last = tick.Last - ticker.ProcessTicker(a.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(a.GetName(), p, tickerPrice, ticker.Spot) + return ticker.GetTicker(a.Name, p, ticker.Spot) } // GetTickerPrice returns the ticker for a currency pair -func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tick, err := ticker.GetTicker(a.GetName(), p) +func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) (ticker.Price, error) { + tick, err := ticker.GetTicker(a.GetName(), p, ticker.Spot) if err != nil { return a.UpdateTicker(p) } @@ -74,9 +74,8 @@ func (a *Alphapoint) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBa orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Quantity, Price: data.Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(a.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(a.Name, p) } // GetOrderbookEx returns the orderbook for a currency pair diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 9688fbab..1843a3c8 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -45,6 +46,7 @@ func (a *ANX) SetDefaults() { a.ConfigCurrencyPairFormat.Delimiter = "" a.ConfigCurrencyPairFormat.Uppercase = true a.ConfigCurrencyPairFormat.Index = "BTC" + a.AssetTypes = []string{ticker.Spot} } //Setup is run on startup to setup exchange with config values @@ -65,6 +67,10 @@ func (a *ANX) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = a.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index e6288c5d..8c6e9c8c 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -3,12 +3,10 @@ package anx import ( "log" "strconv" - "time" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -23,29 +21,12 @@ func (a *ANX) Run() { log.Printf("%s polling delay: %ds.\n", a.GetName(), a.RESTPollingDelay) log.Printf("%s %d currencies enabled: %s.\n", a.GetName(), len(a.EnabledPairs), a.EnabledPairs) } - - for a.Enabled { - pairs := a.GetEnabledCurrencies() - for x := range pairs { - currency := pairs[x] - go func() { - ticker, err := a.UpdateTicker(currency) - if err != nil { - log.Println(err) - return - } - log.Printf("ANX %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) - stats.AddExchangeInfo(a.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) - }() - } - time.Sleep(time.Second * a.RESTPollingDelay) - } } // UpdateTicker updates and returns the ticker for a currency pair -func (a *ANX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice - tick, err := a.GetTicker(p.Pair().String()) +func (a *ANX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price + tick, err := a.GetTicker(exchange.FormatExchangeCurrency(a.GetName(), p).String()) if err != nil { return tickerPrice, err } @@ -105,15 +86,15 @@ func (a *ANX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { } else { tickerPrice.High = 0 } - ticker.ProcessTicker(a.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(a.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(a.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(a.GetName(), p) +func (a *ANX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(a.GetName(), p, assetType) if err != nil { - return a.UpdateTicker(p) + return a.UpdateTicker(p, assetType) } return tickerNew, nil } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index f1e14800..1c6185a4 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -85,6 +86,7 @@ func (b *Bitfinex) SetDefaults() { b.RequestCurrencyPairFormat.Uppercase = true b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} } // Setup takes in the supplied exchange configuration details and sets params @@ -105,6 +107,10 @@ func (b *Bitfinex) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index d09f1020..8a817e52 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -39,8 +39,8 @@ func (b *Bitfinex) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tickerNew, err := b.GetTicker(p.Pair().String(), nil) if err != nil { return tickerPrice, err @@ -53,15 +53,15 @@ func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) tickerPrice.Last = tickerNew.Last tickerPrice.Volume = tickerNew.Volume tickerPrice.High = tickerNew.High - ticker.ProcessTicker(b.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tick, err := ticker.GetTicker(b.GetName(), p) +func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot) if err != nil { - return b.UpdateTicker(p) + return b.UpdateTicker(p, assetType) } return tick, nil } @@ -91,9 +91,8 @@ func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Amount}) } - orderBook.Pair = p orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(b.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies on the diff --git a/exchanges/bitfinex/bitfinex_wrapper_test.go b/exchanges/bitfinex/bitfinex_wrapper_test.go index e1f634ad..4b614392 100644 --- a/exchanges/bitfinex/bitfinex_wrapper_test.go +++ b/exchanges/bitfinex/bitfinex_wrapper_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) func TestStart(t *testing.T) { @@ -18,7 +19,7 @@ func TestRun(t *testing.T) { func TestGetTickerPrice(t *testing.T) { getTickerPrice := Bitfinex{} - _, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD")) + _, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD"), ticker.Spot) if err != nil { t.Errorf("Test Failed - Bitfinex GetTickerPrice() error: %s", err) } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index b10873d4..cafffa04 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -64,6 +65,7 @@ func (b *Bitstamp) SetDefaults() { b.RequestCurrencyPairFormat.Uppercase = true b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} } // Setup sets configuration values to bitstamp @@ -84,6 +86,10 @@ func (b *Bitstamp) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 2e8609ed..31c44b53 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -29,8 +29,8 @@ func (b *Bitstamp) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := b.GetTicker(p.Pair().String(), false) if err != nil { return tickerPrice, err @@ -43,15 +43,15 @@ func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) tickerPrice.Last = tick.Last tickerPrice.Volume = tick.Volume tickerPrice.High = tick.High - ticker.ProcessTicker(b.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tick, err := ticker.GetTicker(b.GetName(), p) +func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { - return b.UpdateTicker(p) + return b.UpdateTicker(p, assetType) } return tick, nil } @@ -83,9 +83,8 @@ func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(b.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index b5ffd7a9..ab5791fb 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -70,6 +71,7 @@ func (b *Bittrex) SetDefaults() { b.RequestCurrencyPairFormat.Uppercase = true b.ConfigCurrencyPairFormat.Delimiter = "-" b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} } // Setup method sets current configuration details if enabled @@ -90,6 +92,10 @@ func (b *Bittrex) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 72797a54..00392a32 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -75,8 +75,8 @@ func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *Bittrex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (b *Bittrex) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := b.GetMarketSummary(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return tickerPrice, err @@ -86,15 +86,15 @@ func (b *Bittrex) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) tickerPrice.Bid = tick[0].Bid tickerPrice.Last = tick[0].Last tickerPrice.Volume = tick[0].Volume - ticker.ProcessTicker(b.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tick, err := ticker.GetTicker(b.GetName(), p) +func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot) if err != nil { - return b.UpdateTicker(p) + return b.UpdateTicker(p, assetType) } return tick, nil } @@ -134,7 +134,6 @@ func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, ) } - orderBook.Pair = p orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(b.Name, p) } diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index ec14b9e7..639499e9 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -59,6 +60,7 @@ func (b *BTCC) SetDefaults() { b.RequestCurrencyPairFormat.Uppercase = false b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} } // Setup is run on startup to setup exchange with config values @@ -79,6 +81,10 @@ func (b *BTCC) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index b009339d..bbab1953 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -29,8 +29,8 @@ func (b *BTCC) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (b *BTCC) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := b.GetTicker(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return tickerPrice, err @@ -42,15 +42,15 @@ func (b *BTCC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.Last = tick.Last tickerPrice.Volume = tick.Vol tickerPrice.High = tick.High - ticker.ProcessTicker(b.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p) +func (b *BTCC) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { - return b.UpdateTicker(p) + return b.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -82,9 +82,8 @@ func (b *BTCC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, er orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } - orderBook.Pair = p orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(b.Name, p) } // GetExchangeAccountInfo : Retrieves balances for all enabled currencies for diff --git a/exchanges/btce/btce.go b/exchanges/btce/btce.go index bebd3674..b251a4b5 100644 --- a/exchanges/btce/btce.go +++ b/exchanges/btce/btce.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -53,6 +54,7 @@ func (b *BTCE) SetDefaults() { b.RequestCurrencyPairFormat.Separator = "-" b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} } func (b *BTCE) Setup(exch config.ExchangeConfig) { @@ -72,6 +74,10 @@ func (b *BTCE) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index b37814a6..0b8e3eed 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -53,8 +53,8 @@ func (b *BTCE) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCE) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (b *BTCE) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price result, err := b.GetTicker(p.Pair().String()) if err != nil { return tickerPrice, err @@ -71,15 +71,15 @@ func (b *BTCE) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.Last = tick.Last tickerPrice.Volume = tick.Vol_cur tickerPrice.High = tick.High - ticker.ProcessTicker(b.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tick, err := ticker.GetTicker(b.GetName(), p) +func (b *BTCE) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { - return b.UpdateTicker(p) + return b.UpdateTicker(p, assetType) } return tick, nil } @@ -111,9 +111,8 @@ func (b *BTCE) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, er orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } - orderBook.Pair = p orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(b.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 9cc8916f..4668fedd 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -56,6 +57,7 @@ func (b *BTCMarkets) SetDefaults() { b.RequestCurrencyPairFormat.Uppercase = true b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true + b.AssetTypes = []string{ticker.Spot} } // Setup takes in an exchange configuration and sets all paramaters @@ -76,6 +78,10 @@ func (b *BTCMarkets) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = b.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index a06b7bcb..841b316b 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -51,8 +51,8 @@ func (b *BTCMarkets) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := b.GetTicker(p.GetFirstCurrency().String()) if err != nil { return tickerPrice, err @@ -61,15 +61,15 @@ func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, erro tickerPrice.Ask = tick.BestAsk tickerPrice.Bid = tick.BestBID tickerPrice.Last = tick.LastPrice - ticker.ProcessTicker(b.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(b.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(b.GetName(), p) +func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType) if err != nil { - return b.UpdateTicker(p) + return b.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -101,9 +101,8 @@ func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBa orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - orderBook.Pair = p orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(b.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index a26d966c..273c6f09 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -52,6 +53,7 @@ func (c *COINUT) SetDefaults() { c.RequestCurrencyPairFormat.Uppercase = true c.ConfigCurrencyPairFormat.Delimiter = "" c.ConfigCurrencyPairFormat.Uppercase = true + c.AssetTypes = []string{ticker.Spot} } func (c *COINUT) Setup(exch config.ExchangeConfig) { @@ -71,6 +73,10 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = c.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/coinut/coinut_types.go b/exchanges/coinut/coinut_types.go index 91b55223..10db1bf9 100644 --- a/exchanges/coinut/coinut_types.go +++ b/exchanges/coinut/coinut_types.go @@ -34,7 +34,7 @@ type CoinutTicker struct { type CoinutOrderbookBase struct { Count int `json:"count"` Price float64 `json:"price,string"` - Quantity float64 `json:"quantity,string"` + Quantity float64 `json:"qty,string"` } type CoinutOrderbook struct { diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index a183016e..bb60982e 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -69,11 +69,11 @@ func (c *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (c *COINUT) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (c *COINUT) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.Pair().String()]) if err != nil { - return ticker.TickerPrice{}, err + return ticker.Price{}, err } tickerPrice.Pair = p @@ -81,16 +81,16 @@ func (c *COINUT) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.Last = tick.Last tickerPrice.High = tick.HighestBuy tickerPrice.Low = tick.LowestSell - ticker.ProcessTicker(c.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(c.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(c.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(c.GetName(), p) +func (c *COINUT) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType) if err != nil { - return c.UpdateTicker(p) + return c.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -120,7 +120,6 @@ func (c *COINUT) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(c.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(c.Name, p) } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 9ebb7ce7..89c859e2 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -49,6 +49,7 @@ type Base struct { BaseCurrencies []string AvailablePairs []string EnabledPairs []string + AssetTypes []string WebsocketURL string APIUrl string RequestCurrencyPairFormat config.CurrencyPairFormatConfig @@ -63,8 +64,8 @@ type IBotExchange interface { SetDefaults() GetName() string IsEnabled() bool - GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) - UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, error) + GetTickerPrice(currency pair.CurrencyPair, assetType string) (ticker.Price, error) + UpdateTicker(currency pair.CurrencyPair, assetType string) (ticker.Price, error) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) UpdateOrderbook(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) GetEnabledCurrencies() []pair.CurrencyPair @@ -72,6 +73,42 @@ type IBotExchange interface { GetAuthenticatedAPISupport() bool } +// SetAssetTypes checks the exchange asset types (whether it supports SPOT, +// Binary or Futures) and sets it to a default setting if it doesn't exist +func (e *Base) SetAssetTypes() error { + cfg := config.GetConfig() + exch, err := cfg.GetExchangeConfig(e.Name) + if err != nil { + return err + } + + update := false + if exch.AssetTypes == "" { + exch.AssetTypes = common.JoinStrings(e.AssetTypes, ",") + update = true + } else { + e.AssetTypes = common.SplitStrings(exch.AssetTypes, ",") + } + + if update { + return cfg.UpdateExchangeConfig(exch) + } + + return nil +} + +// GetExchangeAssetTypes returns the asset types the exchange supports (SPOT, +// binary, futures) +func GetExchangeAssetTypes(exchName string) ([]string, error) { + cfg := config.GetConfig() + exch, err := cfg.GetExchangeConfig(exchName) + if err != nil { + return nil, err + } + + return common.SplitStrings(exch.AssetTypes, ","), nil +} + // SetCurrencyPairFormat checks the exchange request and config currency pair // formats and sets it to a default setting if it doesn't exist func (e *Base) SetCurrencyPairFormat() error { diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index 04a2d361..1ff4f59a 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -50,6 +51,7 @@ func (g *GDAX) SetDefaults() { g.RequestCurrencyPairFormat.Uppercase = true g.ConfigCurrencyPairFormat.Delimiter = "" g.ConfigCurrencyPairFormat.Uppercase = true + g.AssetTypes = []string{ticker.Spot} } func (g *GDAX) Setup(exch config.ExchangeConfig) { @@ -69,6 +71,10 @@ func (g *GDAX) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = g.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 13583d78..f6705edc 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -65,17 +65,17 @@ func (g *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (g *GDAX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (g *GDAX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := g.GetTicker(exchange.FormatExchangeCurrency(g.Name, p).String()) if err != nil { - return ticker.TickerPrice{}, err + return ticker.Price{}, err } stats, err := g.GetStats(exchange.FormatExchangeCurrency(g.Name, p).String()) if err != nil { - return ticker.TickerPrice{}, err + return ticker.Price{}, err } tickerPrice.Pair = p @@ -83,15 +83,15 @@ func (g *GDAX) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.Last = tick.Price tickerPrice.High = stats.High tickerPrice.Low = stats.Low - ticker.ProcessTicker(g.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(g.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(g.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(g.GetName(), p) +func (g *GDAX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) if err != nil { - return g.UpdateTicker(p) + return g.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -108,7 +108,7 @@ func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err // UpdateOrderbook updates and returns the orderbook for a currency pair func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase - orderbookNew, err := g.GetOrderbook(p.Pair().String(), 2) + orderbookNew, err := g.GetOrderbook(exchange.FormatExchangeCurrency(g.Name, p).String(), 2) if err != nil { return orderBook, err } @@ -123,7 +123,6 @@ func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, er orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(g.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(g.Name, p) } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 638ebd6c..35766c8b 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -49,6 +50,7 @@ func (g *Gemini) SetDefaults() { g.RequestCurrencyPairFormat.Uppercase = true g.ConfigCurrencyPairFormat.Delimiter = "" g.ConfigCurrencyPairFormat.Uppercase = true + g.AssetTypes = []string{ticker.Spot} } func (g *Gemini) Setup(exch config.ExchangeConfig) { @@ -68,6 +70,10 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = g.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index b948ce05..19ee46d9 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -53,8 +53,8 @@ func (g *Gemini) GetExchangeAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (g *Gemini) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (g *Gemini) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := g.GetTicker(p.Pair().String()) if err != nil { return tickerPrice, err @@ -64,15 +64,15 @@ func (g *Gemini) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.Bid = tick.Bid tickerPrice.Last = tick.Last tickerPrice.Volume = tick.Volume.USD - ticker.ProcessTicker(g.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(g.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(g.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(g.GetName(), p) +func (g *Gemini) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType) if err != nil { - return g.UpdateTicker(p) + return g.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -102,7 +102,6 @@ func (g *Gemini) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(g.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(g.Name, p) } diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 5b15a233..f03517c4 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -33,6 +34,7 @@ func (h *HUOBI) SetDefaults() { h.RequestCurrencyPairFormat.Uppercase = false h.ConfigCurrencyPairFormat.Delimiter = "" h.ConfigCurrencyPairFormat.Uppercase = true + h.AssetTypes = []string{ticker.Spot} } func (h *HUOBI) Setup(exch config.ExchangeConfig) { @@ -52,6 +54,10 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = h.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 07d36619..10f7d5a4 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -29,8 +29,8 @@ func (h *HUOBI) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (h *HUOBI) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (h *HUOBI) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := h.GetTicker(p.GetFirstCurrency().Lower().String()) if err != nil { return tickerPrice, err @@ -42,15 +42,15 @@ func (h *HUOBI) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.Last = tick.Last tickerPrice.Volume = tick.Vol tickerPrice.High = tick.High - ticker.ProcessTicker(h.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(h.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(h.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(h.GetName(), p) +func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType) if err != nil { - return h.UpdateTicker(p) + return h.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -82,9 +82,8 @@ func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - orderBook.Pair = p orderbook.ProcessOrderbook(h.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(h.Name, p) } //GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index ae2f1b94..80aec2b0 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -35,6 +36,7 @@ func (i *ItBit) SetDefaults() { i.RequestCurrencyPairFormat.Uppercase = true i.ConfigCurrencyPairFormat.Delimiter = "" i.ConfigCurrencyPairFormat.Uppercase = true + i.AssetTypes = []string{ticker.Spot} } func (i *ItBit) Setup(exch config.ExchangeConfig) { @@ -54,6 +56,10 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = i.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 445abc61..871e923e 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -24,9 +24,10 @@ func (i *ItBit) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (i *ItBit) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice - tick, err := i.GetTicker(p.Pair().String()) +func (i *ItBit) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price + tick, err := i.GetTicker(exchange.FormatExchangeCurrency(i.Name, + p).String()) if err != nil { return tickerPrice, err } @@ -38,15 +39,15 @@ func (i *ItBit) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerPrice.High = tick.High24h tickerPrice.Low = tick.Low24h tickerPrice.Volume = tick.Volume24h - ticker.ProcessTicker(i.GetName(), p, tickerPrice) - return tickerPrice, nil + ticker.ProcessTicker(i.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(i.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(i.GetName(), p) +func (i *ItBit) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(i.GetName(), p, assetType) if err != nil { - return i.UpdateTicker(p) + return i.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -63,7 +64,8 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er // UpdateOrderbook updates and returns the orderbook for a currency pair func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase - orderbookNew, err := i.GetOrderbook(p.Pair().String()) + orderbookNew, err := i.GetOrderbook(exchange.FormatExchangeCurrency(i.Name, + p).String()) if err != nil { return orderBook, err } @@ -94,9 +96,8 @@ func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: amount, Price: price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(i.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(i.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index cb90144e..cd264052 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -60,6 +61,7 @@ func (k *Kraken) SetDefaults() { k.RequestCurrencyPairFormat.Separator = "," k.ConfigCurrencyPairFormat.Delimiter = "" k.ConfigCurrencyPairFormat.Uppercase = true + k.AssetTypes = []string{ticker.Spot} } func (k *Kraken) Setup(exch config.ExchangeConfig) { @@ -79,6 +81,10 @@ func (k *Kraken) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = k.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index c05e69c8..c880dafc 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -37,8 +37,8 @@ func (k *Kraken) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (k *Kraken) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price pairs := k.GetEnabledCurrencies() pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs) if err != nil { @@ -50,7 +50,7 @@ func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { } for _, x := range pairs { - var tp ticker.TickerPrice + var tp ticker.Price tick, ok := k.Ticker[x.Pair().String()] if !ok { continue @@ -63,16 +63,16 @@ func (k *Kraken) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { tp.High = tick.High tp.Low = tick.Low tp.Volume = tick.Volume - ticker.ProcessTicker(k.GetName(), x, tp) + ticker.ProcessTicker(k.GetName(), x, tp, assetType) } - return ticker.GetTicker(k.GetName(), p) + return ticker.GetTicker(k.GetName(), p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (k *Kraken) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(k.GetName(), p) +func (k *Kraken) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(k.GetName(), p, assetType) if err != nil { - return k.UpdateTicker(p) + return k.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -102,9 +102,8 @@ func (k *Kraken) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(k.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(k.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 79c0a9c9..62a8f604 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -46,6 +47,7 @@ func (l *LakeBTC) SetDefaults() { l.RequestCurrencyPairFormat.Uppercase = true l.ConfigCurrencyPairFormat.Delimiter = "" l.ConfigCurrencyPairFormat.Uppercase = true + l.AssetTypes = []string{ticker.Spot} } func (l *LakeBTC) Setup(exch config.ExchangeConfig) { @@ -65,6 +67,10 @@ func (l *LakeBTC) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = l.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index e9b463ab..df01c9fa 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -25,34 +25,32 @@ func (l *LakeBTC) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { +func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { tick, err := l.GetTicker() if err != nil { - return ticker.TickerPrice{}, err + return ticker.Price{}, err } - result, ok := tick[p.Pair().String()] - if !ok { - return ticker.TickerPrice{}, err + for _, x := range l.GetEnabledCurrencies() { + currency := exchange.FormatExchangeCurrency(l.Name, x).String() + var tickerPrice ticker.Price + tickerPrice.Pair = x + tickerPrice.Ask = tick[currency].Ask + tickerPrice.Bid = tick[currency].Bid + tickerPrice.Volume = tick[currency].Volume + tickerPrice.High = tick[currency].High + tickerPrice.Low = tick[currency].Low + tickerPrice.Last = tick[currency].Last + ticker.ProcessTicker(l.GetName(), x, tickerPrice, assetType) } - - var tickerPrice ticker.TickerPrice - tickerPrice.Pair = p - tickerPrice.Ask = result.Ask - tickerPrice.Bid = result.Bid - tickerPrice.Volume = result.Volume - tickerPrice.High = result.High - tickerPrice.Low = result.Low - tickerPrice.Last = result.Last - ticker.ProcessTicker(l.GetName(), p, tickerPrice) - return tickerPrice, nil + return ticker.GetTicker(l.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(l.GetName(), p) +func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) if err != nil { - return l.UpdateTicker(p) + return l.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -82,9 +80,8 @@ func (l *LakeBTC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(l.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(l.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 4d610cb6..ca9c5f6a 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -51,6 +52,7 @@ func (l *Liqui) SetDefaults() { l.RequestCurrencyPairFormat.Separator = "-" l.ConfigCurrencyPairFormat.Delimiter = "_" l.ConfigCurrencyPairFormat.Uppercase = true + l.AssetTypes = []string{ticker.Spot} } func (l *Liqui) Setup(exch config.ExchangeConfig) { @@ -70,6 +72,10 @@ func (l *Liqui) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = l.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index ae3d5b79..ba934d99 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -36,8 +36,8 @@ func (l *Liqui) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (l *Liqui) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (l *Liqui) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price pairsString, err := exchange.GetAndFormatExchangeCurrencies(l.Name, l.GetEnabledCurrencies()) if err != nil { @@ -49,34 +49,34 @@ func (l *Liqui) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { return tickerPrice, err } - for x, y := range result { - var tp ticker.TickerPrice - currency := pair.NewCurrencyPairDelimiter(common.StringToUpper(x), "_") - tp.Pair = currency - tp.Last = y.Last - tp.Ask = y.Sell - tp.Bid = y.Buy - tp.Last = y.Last - tp.Low = y.Low - tp.Volume = y.Vol_cur - ticker.ProcessTicker(l.GetName(), currency, tp) + for _, x := range l.GetEnabledCurrencies() { + currency := exchange.FormatExchangeCurrency(l.Name, x).String() + var tp ticker.Price + tp.Pair = x + tp.Last = result[currency].Last + tp.Ask = result[currency].Sell + tp.Bid = result[currency].Buy + tp.Last = result[currency].Last + tp.Low = result[currency].Low + tp.Volume = result[currency].Vol_cur + ticker.ProcessTicker(l.Name, x, tp, assetType) } - return ticker.GetTicker(l.GetName(), p) + return ticker.GetTicker(l.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(l.GetName(), p) +func (l *Liqui) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(l.Name, p, assetType) if err != nil { - return l.UpdateTicker(p) + return l.UpdateTicker(p, assetType) } return tickerNew, nil } // GetOrderbookEx returns orderbook base on the currency pair func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(l.GetName(), p) + ob, err := orderbook.GetOrderbook(l.Name, p) if err == nil { return l.UpdateOrderbook(p) } @@ -101,9 +101,8 @@ func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - orderBook.Pair = p - orderbook.ProcessOrderbook(l.GetName(), p, orderBook) - return orderBook, nil + orderbook.ProcessOrderbook(l.Name, p, orderBook) + return orderbook.GetOrderbook(l.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 5370503e..705c0aca 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -42,6 +43,7 @@ func (l *LocalBitcoins) SetDefaults() { l.RequestCurrencyPairFormat.Uppercase = true l.ConfigCurrencyPairFormat.Delimiter = "" l.ConfigCurrencyPairFormat.Uppercase = true + l.AssetTypes = []string{ticker.Spot} } func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) { @@ -61,6 +63,10 @@ func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = l.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 5a980492..06685353 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -3,8 +3,6 @@ package localbitcoins import ( "log" - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" @@ -25,30 +23,30 @@ func (l *LocalBitcoins) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice +func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := l.GetTicker() if err != nil { return tickerPrice, err } - for key, value := range tick { - currency := pair.NewCurrencyPair("BTC", common.StringToUpper(key)) - var tp ticker.TickerPrice - tp.Pair = currency - tp.Last = value.Rates.Last - tp.Volume = value.VolumeBTC - ticker.ProcessTicker(l.GetName(), currency, tp) + for _, x := range l.GetEnabledCurrencies() { + currency := x.SecondCurrency.String() + var tp ticker.Price + tp.Pair = x + tp.Last = tick[currency].Rates.Last + tp.Volume = tick[currency].VolumeBTC + ticker.ProcessTicker(l.GetName(), x, tp, assetType) } - return ticker.GetTicker(l.GetName(), p) + return ticker.GetTicker(l.GetName(), p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(l.GetName(), p) +func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType) if err == nil { - return l.UpdateTicker(p) + return l.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -77,12 +75,11 @@ func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair) (orderbook.Orderboo for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Bids = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } - orderBook.Pair = p orderbook.ProcessOrderbook(l.GetName(), p, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(l.Name, p) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index 47ed673e..e9b8364b 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -93,8 +94,10 @@ func (o *OKCoin) SetDefaults() { o.Websocket = false o.RESTPollingDelay = 10 o.FuturesValues = []string{"this_week", "next_week", "quarter"} + o.AssetTypes = []string{ticker.Spot} if !okcoinDefaultsSet { + o.AssetTypes = append(o.AssetTypes, o.FuturesValues...) o.APIUrl = OKCOIN_API_URL o.Name = "OKCOIN International" o.WebsocketURL = OKCOIN_WEBSOCKET_URL @@ -125,6 +128,10 @@ func (o *OKCoin) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = o.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 937ed9aa..a1a4807f 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -2,13 +2,11 @@ package okcoin import ( "log" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -28,53 +26,49 @@ func (o *OKCoin) Run() { if o.Websocket { go o.WebsocketClient() } - - for o.Enabled { - pairs := o.GetEnabledCurrencies() - for x := range pairs { - curr := pairs[x] - if o.APIUrl == OKCOIN_API_URL { - for _, y := range o.FuturesValues { - futuresValue := y - go func() { - ticker, err := o.GetFuturesTicker(exchange.FormatExchangeCurrency(o.Name, curr).String(), futuresValue) - if err != nil { - log.Println(err) - return - } - log.Printf("OKCoin Intl Futures %s (%s): Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(curr).String(), futuresValue, ticker.Last, ticker.High, ticker.Low, ticker.Vol) - stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Vol) - }() - } - } - } - time.Sleep(time.Second * o.RESTPollingDelay) - } } // UpdateTicker updates and returns the ticker for a currency pair -func (o *OKCoin) UpdateTicker(currency pair.CurrencyPair) (ticker.TickerPrice, error) { - var tickerPrice ticker.TickerPrice - tick, err := o.GetTicker(exchange.FormatExchangeCurrency(o.Name, currency).String()) - if err != nil { - return tickerPrice, err +func (o *OKCoin) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + currency := exchange.FormatExchangeCurrency(o.Name, p).String() + var tickerPrice ticker.Price + + if assetType != ticker.Spot && o.APIUrl == OKCOIN_API_URL { + tick, err := o.GetFuturesTicker(currency, assetType) + if err != nil { + return tickerPrice, err + } + tickerPrice.Pair = p + tickerPrice.Ask = tick.Sell + tickerPrice.Bid = tick.Buy + tickerPrice.Low = tick.Low + tickerPrice.Last = tick.Last + tickerPrice.Volume = tick.Vol + tickerPrice.High = tick.High + ticker.ProcessTicker(o.GetName(), p, tickerPrice, assetType) + } else { + tick, err := o.GetTicker(currency) + if err != nil { + return tickerPrice, err + } + tickerPrice.Pair = p + tickerPrice.Ask = tick.Sell + tickerPrice.Bid = tick.Buy + tickerPrice.Low = tick.Low + tickerPrice.Last = tick.Last + tickerPrice.Volume = tick.Vol + tickerPrice.High = tick.High + ticker.ProcessTicker(o.GetName(), p, tickerPrice, ticker.Spot) + } - tickerPrice.Pair = currency - tickerPrice.Ask = tick.Sell - tickerPrice.Bid = tick.Buy - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Vol - tickerPrice.High = tick.High - ticker.ProcessTicker(o.GetName(), currency, tickerPrice) - return tickerPrice, nil + return ticker.GetTicker(o.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(o.GetName(), currency) +func (o *OKCoin) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(o.GetName(), p, assetType) if err != nil { - return o.UpdateTicker(currency) + return o.UpdateTicker(p, assetType) } return tickerNew, nil } @@ -106,9 +100,8 @@ func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair) (orderbook.Orderboo orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - orderBook.Pair = currency orderbook.ProcessOrderbook(o.GetName(), currency, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(o.Name, currency) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index d9391a58..50528dac 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -122,6 +122,7 @@ func CreateNewOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew O func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew OrderbookBase) { orderbookNew.CurrencyPair = p.Pair().String() + orderbookNew.LastUpdated = time.Now() if len(Orderbooks) == 0 { CreateNewOrderbook(exchangeName, p, orderbookNew) return diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index e34cde58..3c777206 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( @@ -62,6 +63,7 @@ func (p *Poloniex) SetDefaults() { p.RequestCurrencyPairFormat.Uppercase = true p.ConfigCurrencyPairFormat.Delimiter = "_" p.ConfigCurrencyPairFormat.Uppercase = true + p.AssetTypes = []string{ticker.Spot} } func (p *Poloniex) Setup(exch config.ExchangeConfig) { @@ -81,6 +83,10 @@ func (p *Poloniex) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } + err = p.SetAssetTypes() + if err != nil { + log.Fatal(err) + } } } @@ -133,14 +139,20 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo ob := PoloniexOrderbook{} for x := range resp.Asks { data := resp.Asks[x] - price, _ := strconv.ParseFloat(data[0].(string), 64) + price, err := strconv.ParseFloat(data[0].(string), 64) + if err != nil { + return ob, err + } amount := data[1].(float64) ob.Asks = append(ob.Asks, PoloniexOrderbookItem{Price: price, Amount: amount}) } for x := range resp.Bids { data := resp.Bids[x] - price, _ := strconv.ParseFloat(data[0].(string), 64) + price, err := strconv.ParseFloat(data[0].(string), 64) + if err != nil { + return ob, err + } amount := data[1].(float64) ob.Bids = append(ob.Bids, PoloniexOrderbookItem{Price: price, Amount: amount}) } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index e779f4b8..10896e50 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -29,30 +29,33 @@ func (p *Poloniex) Run() { } // UpdateTicker updates and returns the ticker for a currency pair -func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { - currency := exchange.FormatCurrency(currencyPair).String() - var tickerPrice ticker.TickerPrice +func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price tick, err := p.GetTicker() if err != nil { return tickerPrice, err } - tickerPrice.Pair = currencyPair - tickerPrice.Ask = tick[currency].Last - tickerPrice.Bid = tick[currency].HighestBid - tickerPrice.High = tick[currency].HighestBid - tickerPrice.Last = tick[currency].Last - tickerPrice.Low = tick[currency].LowestAsk - tickerPrice.Volume = tick[currency].BaseVolume - ticker.ProcessTicker(p.GetName(), currencyPair, tickerPrice) - return tickerPrice, nil + for _, x := range p.GetEnabledCurrencies() { + var tp ticker.Price + curr := exchange.FormatExchangeCurrency(p.GetName(), x).String() + tp.Pair = x + tp.Ask = tick[curr].LowestAsk + tp.Bid = tick[curr].HighestBid + tp.High = tick[curr].High24Hr + tp.Last = tick[curr].Last + tp.Low = tick[curr].Low24Hr + tp.Volume = tick[curr].BaseVolume + ticker.ProcessTicker(p.GetName(), x, tp, assetType) + } + return ticker.GetTicker(p.Name, currencyPair, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) { - tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair) +func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair, assetType string) (ticker.Price, error) { + tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair, assetType) if err != nil { - return p.UpdateTicker(currencyPair) + return p.UpdateTicker(currencyPair, assetType) } return tickerNew, nil } @@ -69,7 +72,7 @@ func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.Ord // UpdateOrderbook updates and returns the orderbook for a currency pair func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) { var orderBook orderbook.OrderbookBase - orderbookNew, err := p.GetOrderbook(exchange.FormatCurrency(currencyPair).String(), 1000) + orderbookNew, err := p.GetOrderbook(exchange.FormatExchangeCurrency(p.GetName(), currencyPair).String(), 1000) if err != nil { return orderBook, err } @@ -83,9 +86,9 @@ func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair) (orderbook.Or data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } - orderBook.Pair = currencyPair + orderbook.ProcessOrderbook(p.GetName(), currencyPair, orderBook) - return orderBook, nil + return orderbook.GetOrderbook(p.Name, currencyPair) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 576e13d7..3d2c23c7 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -8,15 +8,22 @@ import ( "github.com/thrasher-/gocryptotrader/currency/pair" ) -var ( +// Const values for the ticker package +const ( ErrTickerForExchangeNotFound = "Ticker for exchange does not exist." ErrPrimaryCurrencyNotFound = "Error primary currency for ticker not found." ErrSecondaryCurrencyNotFound = "Error secondary currency for ticker not found." + Spot = "SPOT" +) + +// Vars for the ticker package +var ( Tickers []Ticker ) -type TickerPrice struct { +// Price struct stores the currency pair and pricing information +type Price struct { Pair pair.CurrencyPair `json:"Pair"` CurrencyPair string `json:"CurrencyPair"` Last float64 `json:"Last"` @@ -28,50 +35,55 @@ type TickerPrice struct { PriceATH float64 `json:"PriceATH"` } +// Ticker struct holds the ticker information for a currency pair and type type Ticker struct { - Price map[pair.CurrencyItem]map[pair.CurrencyItem]TickerPrice + Price map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Price ExchangeName string } -func (t *Ticker) PriceToString(p pair.CurrencyPair, priceType string) string { +// PriceToString returns the string version of a stored price field +func (t *Ticker) PriceToString(p pair.CurrencyPair, priceType, tickerType string) string { priceType = common.StringToLower(priceType) + switch priceType { case "last": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Last, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Last, 'f', -1, 64) case "high": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].High, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].High, 'f', -1, 64) case "low": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Low, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Low, 'f', -1, 64) case "bid": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Bid, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Bid, 'f', -1, 64) case "ask": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Ask, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Ask, 'f', -1, 64) case "volume": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Volume, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Volume, 'f', -1, 64) case "ath": - return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].PriceATH, 'f', -1, 64) + return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].PriceATH, 'f', -1, 64) default: return "" } } -func GetTicker(exchange string, p pair.CurrencyPair) (TickerPrice, error) { +// GetTicker checks and returns a requested ticker if it exists +func GetTicker(exchange string, p pair.CurrencyPair, tickerType string) (Price, error) { ticker, err := GetTickerByExchange(exchange) if err != nil { - return TickerPrice{}, err + return Price{}, err } - if !FirstCurrencyExists(exchange, p.GetFirstCurrency()) { - return TickerPrice{}, errors.New(ErrPrimaryCurrencyNotFound) + if !FirstCurrencyExists(exchange, p.FirstCurrency) { + return Price{}, errors.New(ErrPrimaryCurrencyNotFound) } if !SecondCurrencyExists(exchange, p) { - return TickerPrice{}, errors.New(ErrSecondaryCurrencyNotFound) + return Price{}, errors.New(ErrSecondaryCurrencyNotFound) } - return ticker.Price[p.GetFirstCurrency()][p.GetSecondCurrency()], nil + return ticker.Price[p.FirstCurrency][p.SecondCurrency][tickerType], nil } +// GetTickerByExchange returns an exchange Ticker func GetTickerByExchange(exchange string) (*Ticker, error) { for _, y := range Tickers { if y.ExchangeName == exchange { @@ -81,6 +93,8 @@ func GetTickerByExchange(exchange string) (*Ticker, error) { return nil, errors.New(ErrTickerForExchangeNotFound) } +// FirstCurrencyExists checks to see if the first currency of the Price map +// exists func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool { for _, y := range Tickers { if y.ExchangeName == exchange { @@ -92,6 +106,8 @@ func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool { return false } +// SecondCurrencyExists checks to see if the second currency of the Price map +// exists func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool { for _, y := range Tickers { if y.ExchangeName == exchange { @@ -105,40 +121,49 @@ func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool { return false } -func CreateNewTicker(exchangeName string, p pair.CurrencyPair, tickerNew TickerPrice) Ticker { +// CreateNewTicker creates a new Ticker +func CreateNewTicker(exchangeName string, p pair.CurrencyPair, tickerNew Price, tickerType string) Ticker { ticker := Ticker{} ticker.ExchangeName = exchangeName - ticker.Price = make(map[pair.CurrencyItem]map[pair.CurrencyItem]TickerPrice) - sMap := make(map[pair.CurrencyItem]TickerPrice) - sMap[p.GetSecondCurrency()] = tickerNew - ticker.Price[p.GetFirstCurrency()] = sMap + ticker.Price = make(map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Price) + a := make(map[pair.CurrencyItem]map[string]Price) + b := make(map[string]Price) + b[tickerType] = tickerNew + a[p.SecondCurrency] = b + ticker.Price[p.FirstCurrency] = a Tickers = append(Tickers, ticker) return ticker } -func ProcessTicker(exchangeName string, p pair.CurrencyPair, tickerNew TickerPrice) { +// ProcessTicker processes incoming tickers, creating or updating the Tickers +// list +func ProcessTicker(exchangeName string, p pair.CurrencyPair, tickerNew Price, tickerType string) { tickerNew.CurrencyPair = p.Pair().String() if len(Tickers) == 0 { - CreateNewTicker(exchangeName, p, tickerNew) - //issue - not appending + CreateNewTicker(exchangeName, p, tickerNew, tickerType) return - } else { - ticker, err := GetTickerByExchange(exchangeName) - if err != nil { - CreateNewTicker(exchangeName, p, tickerNew) + } + + ticker, err := GetTickerByExchange(exchangeName) + if err != nil { + CreateNewTicker(exchangeName, p, tickerNew, tickerType) + return + } + + if FirstCurrencyExists(exchangeName, p.FirstCurrency) { + if !SecondCurrencyExists(exchangeName, p) { + a := ticker.Price[p.FirstCurrency] + b := make(map[string]Price) + b[tickerType] = tickerNew + a[p.SecondCurrency] = b + ticker.Price[p.FirstCurrency] = a return } - - if FirstCurrencyExists(exchangeName, p.GetFirstCurrency()) { - if !SecondCurrencyExists(exchangeName, p) { - second := ticker.Price[p.GetFirstCurrency()] - second[p.GetSecondCurrency()] = tickerNew - ticker.Price[p.GetFirstCurrency()] = second - return - } - } - sMap := make(map[pair.CurrencyItem]TickerPrice) - sMap[p.GetSecondCurrency()] = tickerNew - ticker.Price[p.GetFirstCurrency()] = sMap } + + a := make(map[pair.CurrencyItem]map[string]Price) + b := make(map[string]Price) + b[tickerType] = tickerNew + a[p.SecondCurrency] = b + ticker.Price[p.FirstCurrency] = a } diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 789a751b..eb7c8b91 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -9,7 +9,7 @@ import ( func TestPriceToString(t *testing.T) { newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -21,37 +21,37 @@ func TestPriceToString(t *testing.T) { PriceATH: 1337, } - newTicker := CreateNewTicker("ANX", newPair, priceStruct) + newTicker := CreateNewTicker("ANX", newPair, priceStruct, Spot) - if newTicker.PriceToString(newPair, "last") != "1200" { + if newTicker.PriceToString(newPair, "last", Spot) != "1200" { t.Error("Test Failed - ticker PriceToString last value is incorrect") } - if newTicker.PriceToString(newPair, "high") != "1298" { + if newTicker.PriceToString(newPair, "high", Spot) != "1298" { t.Error("Test Failed - ticker PriceToString high value is incorrect") } - if newTicker.PriceToString(newPair, "low") != "1148" { + if newTicker.PriceToString(newPair, "low", Spot) != "1148" { t.Error("Test Failed - ticker PriceToString low value is incorrect") } - if newTicker.PriceToString(newPair, "bid") != "1195" { + if newTicker.PriceToString(newPair, "bid", Spot) != "1195" { t.Error("Test Failed - ticker PriceToString bid value is incorrect") } - if newTicker.PriceToString(newPair, "ask") != "1220" { + if newTicker.PriceToString(newPair, "ask", Spot) != "1220" { t.Error("Test Failed - ticker PriceToString ask value is incorrect") } - if newTicker.PriceToString(newPair, "volume") != "5" { + if newTicker.PriceToString(newPair, "volume", Spot) != "5" { t.Error("Test Failed - ticker PriceToString volume value is incorrect") } - if newTicker.PriceToString(newPair, "ath") != "1337" { + if newTicker.PriceToString(newPair, "ath", Spot) != "1337" { t.Error("Test Failed - ticker PriceToString ath value is incorrect") } - if newTicker.PriceToString(newPair, "obtuse") != "" { + if newTicker.PriceToString(newPair, "obtuse", Spot) != "" { t.Error("Test Failed - ticker PriceToString obtuse value is incorrect") } } func TestGetTicker(t *testing.T) { newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -63,21 +63,30 @@ func TestGetTicker(t *testing.T) { PriceATH: 1337, } - bitfinexTicker := CreateNewTicker("bitfinex", newPair, priceStruct) - Tickers = append(Tickers, bitfinexTicker) - - tickerPrice, err := GetTicker("bitfinex", newPair) + ProcessTicker("bitfinex", newPair, priceStruct, Spot) + tickerPrice, err := GetTicker("bitfinex", newPair, Spot) if err != nil { t.Errorf("Test Failed - Ticker GetTicker init error: %s", err) } if tickerPrice.CurrencyPair != "BTCUSD" { t.Error("Test Failed - ticker tickerPrice.CurrencyPair value is incorrect") } + + priceStruct.PriceATH = 9001 + ProcessTicker("bitfinex", newPair, priceStruct, "futures_3m") + tickerPrice, err = GetTicker("bitfinex", newPair, "futures_3m") + if err != nil { + t.Errorf("Test Failed - Ticker GetTicker init error: %s", err) + } + + if tickerPrice.PriceATH != 9001 { + t.Error("Test Failed - ticker tickerPrice.PriceATH value is incorrect") + } } func TestGetTickerByExchange(t *testing.T) { newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -89,7 +98,7 @@ func TestGetTickerByExchange(t *testing.T) { PriceATH: 1337, } - anxTicker := CreateNewTicker("ANX", newPair, priceStruct) + anxTicker := CreateNewTicker("ANX", newPair, priceStruct, Spot) Tickers = append(Tickers, anxTicker) tickerPtr, err := GetTickerByExchange("ANX") @@ -103,7 +112,7 @@ func TestGetTickerByExchange(t *testing.T) { func TestFirstCurrencyExists(t *testing.T) { newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -115,7 +124,7 @@ func TestFirstCurrencyExists(t *testing.T) { PriceATH: 1337, } - alphaTicker := CreateNewTicker("alphapoint", newPair, priceStruct) + alphaTicker := CreateNewTicker("alphapoint", newPair, priceStruct, Spot) Tickers = append(Tickers, alphaTicker) if !FirstCurrencyExists("alphapoint", "BTC") { @@ -130,7 +139,7 @@ func TestSecondCurrencyExists(t *testing.T) { t.Parallel() newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -142,7 +151,7 @@ func TestSecondCurrencyExists(t *testing.T) { PriceATH: 1337, } - bitstampTicker := CreateNewTicker("bitstamp", newPair, priceStruct) + bitstampTicker := CreateNewTicker("bitstamp", newPair, priceStruct, "SPOT") Tickers = append(Tickers, bitstampTicker) if !SecondCurrencyExists("bitstamp", newPair) { @@ -157,7 +166,7 @@ func TestSecondCurrencyExists(t *testing.T) { func TestCreateNewTicker(t *testing.T) { newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -169,7 +178,7 @@ func TestCreateNewTicker(t *testing.T) { PriceATH: 1337, } - newTicker := CreateNewTicker("ANX", newPair, priceStruct) + newTicker := CreateNewTicker("ANX", newPair, priceStruct, Spot) if reflect.ValueOf(newTicker).NumField() != 2 { t.Error("Test Failed - ticker CreateNewTicker struct change/or updated") @@ -181,38 +190,38 @@ func TestCreateNewTicker(t *testing.T) { t.Error("Test Failed - ticker CreateNewTicker.ExchangeName value is not ANX") } - if newTicker.Price["BTC"]["USD"].Pair.Pair().String() != "BTCUSD" { + if newTicker.Price["BTC"]["USD"][Spot].Pair.Pair().String() != "BTCUSD" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Pair.Pair().String() value is not expected 'BTCUSD'") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Ask).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Ask).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Ask value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Bid).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Bid).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Bid value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].CurrencyPair).String() != "string" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].CurrencyPair).String() != "string" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].CurrencyPair value is not a string") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].High).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].High).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].High value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Last).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Last).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Last value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Low).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Low).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Low value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].PriceATH).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].PriceATH).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].PriceATH value is not a float64") } - if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Volume).String() != "float64" { + if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Volume).String() != "float64" { t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Volume value is not a float64") } } func TestProcessTicker(t *testing.T) { //non-appending function to tickers newPair := pair.NewCurrencyPair("BTC", "USD") - priceStruct := TickerPrice{ + priceStruct := Price{ Pair: newPair, CurrencyPair: newPair.Pair().String(), Last: 1200, @@ -224,5 +233,5 @@ func TestProcessTicker(t *testing.T) { //non-appending function to tickers PriceATH: 1337, } - ProcessTicker("btcc", newPair, priceStruct) + ProcessTicker("btcc", newPair, priceStruct, Spot) } diff --git a/main.go b/main.go index 08a6b9d8..97b9dbfd 100644 --- a/main.go +++ b/main.go @@ -191,7 +191,7 @@ func main() { go WebsocketHandler() go TickerUpdaterRoutine() - go OrderbookUpdaterRoutine() + //go OrderbookUpdaterRoutine() if bot.config.Webserver.Enabled { err := bot.config.CheckWebserverConfigValues() diff --git a/routines.go b/routines.go index 51cbd657..0da929fe 100644 --- a/routines.go +++ b/routines.go @@ -1,12 +1,50 @@ package main import ( + "fmt" "log" "time" + "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +func printSummary(result ticker.Price, p pair.CurrencyPair, assetType, exchangeName string, err error) { + if err != nil { + log.Printf("failed to get %s %s ticker. Error: %s", + p.Pair().String(), + exchangeName, + err) + return + } + + log.Printf("%s %s %s: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + result.Last, + result.Ask, + result.Bid, + result.High, + result.Low, + result.Volume) +} + +func relayWebsocketEvent(result interface{}, event, assetType, exchangeName string) { + evt := WebsocketEvent{ + Data: result, + Event: event, + AssetType: assetType, + Exchange: exchangeName, + } + err := BroadcastWebsocketMessage(evt) + if err != nil { + log.Println(fmt.Errorf("Failed to broadcast websocket event. Error: %s", + err)) + } +} + func TickerUpdaterRoutine() { log.Println("Starting ticker updater routine") for { @@ -15,30 +53,34 @@ func TickerUpdaterRoutine() { exchangeName := bot.exchanges[x].GetName() enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() + var result ticker.Price + var err error + var assetTypes []string + for y := range enabledCurrencies { currency := enabledCurrencies[y] - result, err := bot.exchanges[x].UpdateTicker(currency) + assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName) if err != nil { - log.Printf("failed to get %s ticker", currency.Pair().String()) - continue + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) } - - log.Printf("%s %s: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", - exchangeName, - exchange.FormatCurrency(currency).String(), - result.Last, - result.Ask, - result.Bid, - result.High, - result.Low, - result.Volume) - - evt := WebsocketEvent{ - Data: result, - Event: "ticker_update", - Exchange: exchangeName, + if len(assetTypes) > 1 { + for z := range assetTypes { + result, err = bot.exchanges[x].UpdateTicker(currency, + assetTypes[z]) + printSummary(result, currency, assetTypes[z], exchangeName, err) + if err == nil { + relayWebsocketEvent(result, "ticker_update", assetTypes[z], exchangeName) + } + } + } else { + result, err = bot.exchanges[x].UpdateTicker(currency, + assetTypes[0]) + printSummary(result, currency, assetTypes[0], exchangeName, err) + if err == nil { + relayWebsocketEvent(result, "ticker_update", assetTypes[0], exchangeName) + } } - BroadcastWebsocketMessage(evt) } } } @@ -52,6 +94,11 @@ func OrderbookUpdaterRoutine() { for x := range bot.exchanges { if bot.exchanges[x].IsEnabled() { exchangeName := bot.exchanges[x].GetName() + + if exchangeName == "ANX" { + continue + } + enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() for y := range enabledCurrencies { diff --git a/testdata/configtest.dat b/testdata/configtest.dat index 48c98e84..e894e05e 100644 --- a/testdata/configtest.dat +++ b/testdata/configtest.dat @@ -65,6 +65,7 @@ "AvailablePairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", "EnabledPairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC", "BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Index": "BTC" @@ -86,6 +87,7 @@ "AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BCCBTC,BCUBTC,BCCUSD,BCUUSD,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH", "EnabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -106,6 +108,7 @@ "AvailablePairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "EnabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "BaseCurrencies": "USD,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -125,6 +128,7 @@ "AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-VRC,BTC-CURE,BTC-XBB,BTC-XMR,BTC-CLOAK,BTC-START,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-BTCD,BTC-VIA,BTC-UNO,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BLITZ,BTC-BAY,BTC-BTS,BTC-FAIR,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-SNRG,BTC-PKB,BTC-CPC,BTC-AEON,BTC-ETH,BTC-GCR,BTC-TX,BTC-BCY,BTC-EXP,BTC-INFX,BTC-OMNI,BTC-AMP,BTC-AGRS,BTC-XLM,BTC-BTA,USDT-BTC,BTC-CLUB,BTC-VOX,BTC-EMC,BTC-FCT,BTC-MAID,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-SAFEX,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-XVC,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-PDC,BTC-BRK,BTC-DGD,ETH-DGD,BTC-WAVES,BTC-RISE,BTC-LBC,BTC-SBD,BTC-BRX,BTC-DRACO,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-TRIG,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-XAUR,BTC-SNGLS,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-DAR,BTC-GOLOS,BTC-HKG,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-TIME,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-SNGLS,ETH-GNO,BTC-APX,BTC-TKN,ETH-TKN,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,ETH-1ST,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-MYST,ETH-MYST,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-TIME,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-FUN,ETH-FUN,BTC-PAY,ETH-PAY,BTC-MTL,ETH-MTL,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,ETH-BTS", "EnabledPairs": "USDT-BTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Delimiter": "-" @@ -146,6 +150,7 @@ "AvailablePairs": "BTCCNY,LTCCNY,LTCBTC", "EnabledPairs": "BTCCNY,LTCCNY,LTCBTC", "BaseCurrencies": "CNY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -165,6 +170,7 @@ "AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", "EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", "BaseCurrencies": "USD,RUR,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -186,6 +192,7 @@ "AvailablePairs": "LTCAUD,BTCAUD", "EnabledPairs": "LTCAUD,BTCAUD", "BaseCurrencies": "AUD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -206,6 +213,7 @@ "AvailablePairs": "LTCEUR,LTCBTC,BTCGBP,BTCEUR,ETHEUR,ETHBTC,LTCUSD,BTCUSD,ETHUSD", "EnabledPairs": "BTCUSD,BTCGBP,BTCEUR", "BaseCurrencies": "USD,GBP,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -226,6 +234,7 @@ "AvailablePairs": "BTCUSD,ETHBTC,ETHUSD", "EnabledPairs": "BTCUSD", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -245,6 +254,7 @@ "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", "BaseCurrencies": "CNY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -265,6 +275,7 @@ "AvailablePairs": "XBTUSD,XBTSGD,XBTEUR", "EnabledPairs": "XBTUSD,XBTSGD,XBTEUR", "BaseCurrencies": "USD,SGD,EUR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -281,9 +292,10 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "REPETH,ZECXBT,ETCUSD,ETHCAD,ETCEUR,ETHXBT.D,XBTJPY.D,EOSXBT,USDTUSD,LTCEUR,XBTUSD,ETHUSD,XBTEUR.D,BCHEUR,GNOXBT,ICNXBT,XBTEUR,ZECUSD,ETCXBT,ICNETH,LTCXBT,XRPXBT,ZECEUR,DASHUSD,ETHEUR.D,ETHJPY.D,LTCUSD,XMRXBT,BCHXBT,ETHJPY,GNOETH,XDGXBT,ETHCAD.D,XRPUSD,ETHEUR,XMRUSD,MLNETH,REPEUR,XBTCAD.D,XRPEUR,BCHUSD,ETHXBT,XBTJPY,XBTUSD.D,XLMXBT,DASHXBT,XBTGBP.D,MLNXBT,REPXBT,XBTCAD,DASHEUR,ETHGBP.D,ETHUSD.D,XMREUR,EOSETH,ETCETH", + "AvailablePairs": "ETHEUR,XRPXBT,BCHXBT,DASHUSD,EOSETH,REPXBT,XBTUSD.D,XLMXBT,ETHGBP.D,XMRXBT,GNOXBT,ETHUSD,ETCXBT,ETHEUR.D,ICNXBT,XBTJPY.D,XRPUSD,BCHEUR,DASHXBT,ETHCAD,ZECUSD,ICNETH,MLNETH,XDGXBT,GNOETH,LTCUSD,XBTCAD,XBTEUR,ZECXBT,BCHUSD,DASHEUR,EOSXBT,USDTUSD,ETCUSD,ETHXBT,ETHXBT.D,XBTJPY,XBTCAD.D,XRPEUR,LTCXBT,REPETH,XBTGBP.D,REPEUR,XMRUSD,ETHCAD.D,ETHJPY,ETHJPY.D,ETCETH,XBTEUR.D,XBTGBP,LTCEUR,MLNXBT,XBTUSD,XMREUR,ZECEUR,ETCEUR,ETHGBP,ETHUSD.D", "EnabledPairs": "ETCUSD,XBTUSD,ETHUSD", "BaseCurrencies": "EUR,USD,CAD,GBP,JPY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -304,6 +316,7 @@ "AvailablePairs": "BTCUSD,BTCEUR,USDHKD,AUDUSD,BTCGBP,BTCNZD,USDJPY,BTCSGD,BTCNGN,EURUSD,USDSGD,NZDUSD,USDNGN,USDCHF,BTCJPY,BTCAUD,BTCCAD,BTCCHF,GBPUSD,USDCAD", "EnabledPairs": "BTCUSD,BTCAUD", "BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -320,9 +333,10 @@ "AuthenticatedAPISupport": false, "APIKey": "Key", "APISecret": "Secret", - "AvailablePairs": "XID_BTC,OAX_ETH,LTC_BTC,TIME_BTC,GNT_ETH,LUN_BTC,HMQ_ETH,EOS_USDT,NET_ETH,DASH_BTC,ETH_USDT,VSL_USDT,BAT_ETH,OMG_ETH,SAN_ETH,DGD_ETH,STX_ETH,LTC_USDT,TAAS_USDT,BAT_USDT,QRL_USDT,EOS_BTC,NET_USDT,GNT_BTC,ANT_BTC,ANT_USDT,SNGLS_ETH,CFI_ETH,CFI_USDT,PLU_ETH,BTC_USDT,ROUND_USDT,GNO_USDT,ZRX_USDT,ROUND_BTC,ICN_ETH,WAVES_USDT,OMG_BTC,QTUM_BTC,LTC_ETH,EDG_USDT,WINGS_ETH,TKN_BTC,CVC_USDT,REP_USDT,RLC_BTC,GNO_BTC,ADX_USDT,OMG_USDT,PLU_BTC,EDG_BTC,BNT_ETH,OAX_BTC,STX_BTC,BAT_BTC,BCC_USDT,VSL_ETH,REP_ETH,HMQ_BTC,SNT_ETH,ZRX_BTC,MLN_ETH,LUN_ETH,TKN_ETH,CFI_BTC,DGD_USDT,DNT_ETH,STORJ_ETH,TAAS_BTC,HMQ_USDT,BCAP_BTC,BNT_USDT,MYST_BTC,SNM_BTC,ADX_ETH,CVC_BTC,WAVES_BTC,TRST_ETH,PLU_USDT,GNT_USDT,REP_BTC,QRL_BTC,MGO_USDT,BCC_ETH,ZRX_ETH,ICN_BTC,MLN_BTC,RLC_USDT,TAAS_ETH,PAY_BTC,SAN_USDT,WINGS_BTC,1ST_ETH,LUN_USDT,QRL_ETH,SNGLS_USDT,GUP_BTC,PTOY_USDT,MCO_USDT,ICN_USDT,INCNT_USDT,STORJ_USDT,INCNT_BTC,DASH_ETH,GUP_ETH,SNT_BTC,SNT_USDT,ADX_BTC,DGD_BTC,BCC_BTC,VSL_BTC,WINGS_USDT,GUP_USDT,MYST_USDT,PAY_USDT,XID_USDT,MYST_ETH,SNGLS_BTC,SNM_ETH,CVC_ETH,PTOY_BTC,SNM_USDT,1ST_BTC,TIME_ETH,1ST_USDT,MLN_USDT,RLC_ETH,BCAP_ETH,XID_ETH,QTUM_ETH,WAVES_ETH,GNO_ETH,BCAP_USDT,SAN_BTC,TIME_USDT,STORJ_BTC,QTUM_USDT,ROUND_ETH,EDG_ETH,PTOY_ETH,TKN_USDT,BNT_BTC,MGO_BTC,PAY_ETH,INCNT_ETH,DASH_USDT,MCO_BTC,OAX_USDT,DNT_BTC,DNT_USDT,MCO_ETH,EOS_ETH,ETH_BTC,TRST_BTC,TRST_USDT,ANT_ETH,MGO_ETH,NET_BTC,STX_USDT", + "AvailablePairs": "HMQ_ETH,PTOY_ETH,SNT_BTC,TRST_ETH,RLC_BTC,TRST_BTC,LUN_ETH,XID_USDT,DASH_BTC,ICN_USDT,BNT_ETH,TIME_ETH,VSL_BTC,PLU_ETH,1ST_USDT,RLC_ETH,GNO_ETH,TKN_BTC,BCC_ETH,GNT_BTC,ROUND_ETH,EDG_BTC,PAY_USDT,INCNT_USDT,DGD_USDT,LTC_BTC,DASH_USDT,MCO_USDT,OMG_ETH,CVC_BTC,BCC_BTC,DNT_BTC,INCNT_ETH,GUP_BTC,TAAS_ETH,QRL_BTC,ZRX_USDT,1ST_ETH,MYST_ETH,TNT_USDT,STORJ_ETH,NET_USDT,OAX_ETH,OAX_USDT,ZRX_BTC,GNO_BTC,CFI_BTC,NET_ETH,TAAS_USDT,WINGS_ETH,HMQ_BTC,BAT_BTC,PTOY_BTC,PAY_BTC,1ST_BTC,ROUND_USDT,SNGLS_ETH,SNM_ETH,NET_BTC,BTC_USDT,TKN_ETH,HMQ_USDT,MGO_USDT,WINGS_USDT,MGO_BTC,ADX_USDT,DASH_ETH,VSL_ETH,GNT_USDT,MLN_USDT,RLC_USDT,TKN_USDT,ZRX_ETH,ROUND_BTC,QTUM_USDT,STORJ_BTC,MCO_BTC,MCO_ETH,ADX_BTC,EOS_USDT,XID_ETH,STX_USDT,ETH_BTC,MLN_ETH,EDG_USDT,PLU_USDT,LUN_USDT,ANT_USDT,SAN_ETH,TIME_BTC,WAVES_ETH,REP_USDT,BCAP_BTC,SNM_USDT,SNT_USDT,TNT_BTC,WAVES_BTC,GUP_ETH,BCAP_ETH,BNT_BTC,BNT_USDT,SNT_ETH,XID_BTC,DGD_BTC,ICN_ETH,DGD_ETH,LTC_USDT,TIME_USDT,REP_ETH,ANT_ETH,BAT_ETH,ADX_ETH,SAN_BTC,ICN_BTC,QTUM_BTC,MGO_ETH,MYST_USDT,EOS_BTC,OMG_USDT,OAX_BTC,MLN_BTC,TAAS_BTC,WINGS_BTC,SNGLS_BTC,CFI_USDT,SNM_BTC,EOS_ETH,STX_ETH,QRL_ETH,CFI_ETH,STORJ_USDT,SAN_USDT,DNT_USDT,LTC_ETH,VSL_USDT,WAVES_USDT,TRST_USDT,PTOY_USDT,ETH_USDT,GUP_USDT,PAY_ETH,OMG_BTC,INCNT_BTC,CVC_ETH,GNT_ETH,REP_BTC,GNO_USDT,LUN_BTC,MYST_BTC,SNGLS_USDT,QTUM_ETH,PLU_BTC,BCC_USDT,BCAP_USDT,ANT_BTC,BAT_USDT,QRL_USDT,CVC_USDT,DNT_ETH,STX_BTC,EDG_ETH,TNT_ETH", "EnabledPairs": "ETH_BTC,LTC_BTC,DASH_BTC", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Delimiter": "_" @@ -345,6 +359,7 @@ "AvailablePairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", "EnabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR", "BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -364,6 +379,7 @@ "AvailablePairs": "BTCCNY,LTCCNY", "EnabledPairs": "BTCCNY,LTCCNY", "BaseCurrencies": "CNY", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -384,6 +400,7 @@ "AvailablePairs": "BTCUSD,LTCUSD", "EnabledPairs": "BTCUSD,LTCUSD", "BaseCurrencies": "USD", + "AssetTypes": "SPOT,this_week,next_week,quarter", "ConfigCurrencyPairFormat": { "Uppercase": true }, @@ -404,6 +421,7 @@ "AvailablePairs": "BTC_XUSD,BTC_FCT,BTC_MMNXT,BTC_NMC,BTC_BITUSD,BTC_RDD,BTC_XMR,BTC_XST,BTC_DSH,BTC_MAID,BTC_DGB,BTC_NEOS,BTC_BLK,BTC_NAUT,BTC_NBT,BTC_XCP,BTC_STR,BTC_BTCD,BTC_GRC,BTC_HUC,BTC_BBR,BTC_XDN,BTC_INDEX,BTC_IOC,BTC_SWARM,BTC_EMC2,BTC_MCN,BTC_NOXT,BTC_MINT,BTC_PTS,BTC_SC,BTC_GEO,BTC_XRP,BTC_FLO,BTC_BITS,BTC_HYP,BTC_XCR,BTC_LTBC,BTC_SYS,BTC_GMC,BTC_ETH,BTC_SYNC,BTC_GAP,BTC_BCN,BTC_C2,BTC_PINK,BTC_FIBRE,BTC_POT,BTC_QTL,BTC_SDC,BTC_XC,BTC_DASH,BTC_SILK,BTC_CLAM,BTC_NAV,BTC_PIGGY,BTC_BCY,BTC_MIL,BTC_XCN,BTC_YACC,BTC_BTS,BTC_QBK,BTC_SJCX,BTC_LQD,BTC_BURST,BTC_RIC,BTC_VRC,BTC_LTC,BTC_XPB,BTC_GRS,BTC_XCH,BTC_ARCH,BTC_QORA,BTC_HZ,BTC_NSR,BTC_XPM,BTC_BITCNY,BTC_EXE,BTC_XMG,BTC_BTC,BTC_BTM,BTC_NOBL,BTC_NXT,BTC_DOGE,BTC_CURE,BTC_MNTA,BTC_ADN,BTC_EXP,BTC_VTC,BTC_FLDC,BTC_MRS,BTC_MYR,BTC_OMNI,BTC_VNL,BTC_USDT,BTC_NOTE,BTC_WDC,BTC_BELA,BTC_VIA,BTC_CGA,BTC_DIEM,BTC_IFC,BTC_XDP,BTC_BLOCK,BTC_MMC,BTC_1CR,BTC_UNITY,BTC_XBC,BTC_GEMZ,BTC_FLT,BTC_PPC,BTC_XEM,BTC_RBY,BTC_CNMT,BTC_ABY,XMR_XDN,XMR_IFC,XMR_DIEM,XMR_BBR,XMR_DSH,XMR_BCN,XMR_LTC,XMR_MAID,XMR_DASH,XMR_BTCD,XMR_HYP,XMR_BLK,XMR_QORA,XMR_MNTA,XMR_NXT,USDT_BTC,USDT_ETH,USDT_XRP,USDT_DASH,USDT_LTC,USDT_NXT,USDT_XMR,USDT_STR", "EnabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", "BaseCurrencies": "USD", + "AssetTypes": "SPOT", "ConfigCurrencyPairFormat": { "Uppercase": true, "Delimiter": "_" diff --git a/ticker_routes.go b/ticker_routes.go index 7da2a1a7..7c49949c 100644 --- a/ticker_routes.go +++ b/ticker_routes.go @@ -7,17 +7,19 @@ import ( "github.com/gorilla/mux" "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -func GetSpecificTicker(currency, exchangeName string) (ticker.TickerPrice, error) { - var specificTicker ticker.TickerPrice +func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) { + var specificTicker ticker.Price var err error for i := 0; i < len(bot.exchanges); i++ { if bot.exchanges[i] != nil { if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { specificTicker, err = bot.exchanges[i].GetTickerPrice( pair.NewCurrencyPairFromString(currency), + assetType, ) break } @@ -30,7 +32,12 @@ func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) currency := vars["currency"] exchange := vars["exchangeName"] - response, err := GetSpecificTicker(currency, exchange) + assetType := vars["assetType"] + + if assetType == "" { + assetType = ticker.Spot + } + response, err := GetSpecificTicker(currency, exchange, assetType) if err != nil { log.Printf("Failed to fetch ticker for %s currency: %s\n", exchange, currency) @@ -51,8 +58,8 @@ type AllEnabledExchangeCurrencies struct { // EnabledExchangeCurrencies is a sub type for singular exchanges and respective // currencies type EnabledExchangeCurrencies struct { - ExchangeName string `json:"exchangeName"` - ExchangeValues []ticker.TickerPrice `json:"exchangeValues"` + ExchangeName string `json:"exchangeName"` + ExchangeValues []ticker.Price `json:"exchangeValues"` } func GetAllActiveTickers() []EnabledExchangeCurrencies { @@ -61,16 +68,36 @@ func GetAllActiveTickers() []EnabledExchangeCurrencies { for _, individualBot := range bot.exchanges { if individualBot != nil && individualBot.IsEnabled() { var individualExchange EnabledExchangeCurrencies - individualExchange.ExchangeName = individualBot.GetName() + exchangeName := individualBot.GetName() + individualExchange.ExchangeName = exchangeName log.Println( - "Getting enabled currencies for '" + individualBot.GetName() + "'", + "Getting enabled currencies for '" + exchangeName + "'", ) currencies := individualBot.GetEnabledCurrencies() - for x := range currencies { - currency := currencies[x] - - tickerPrice, err := individualBot.GetTickerPrice(currency) + for _, x := range currencies { + currency := x + assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) if err != nil { + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) + continue + } + var tickerPrice ticker.Price + if len(assetTypes) > 1 { + for y := range assetTypes { + tickerPrice, err = individualBot.UpdateTicker(currency, + assetTypes[y]) + } + } else { + tickerPrice, err = individualBot.UpdateTicker(currency, + assetTypes[0]) + } + + if err != nil { + log.Printf("failed to get %s %s ticker. Error: %s", + currency.Pair().String(), + exchangeName, + err) continue } diff --git a/websocket.go b/websocket.go index ccdb4499..3086216a 100644 --- a/websocket.go +++ b/websocket.go @@ -32,9 +32,10 @@ type WebsocketClient struct { } type WebsocketEvent struct { - Exchange string `json:"exchange,omitempty"` - Event string - Data interface{} + Exchange string `json:"exchange,omitempty"` + AssetType string `json:"assetType,omitempty"` + Event string + Data interface{} } type WebsocketEventResponse struct { @@ -44,8 +45,9 @@ type WebsocketEventResponse struct { } type WebsocketTickerRequest struct { - Exchange string `json:"exchangeName"` - Currency string `json:"currency"` + Exchange string `json:"exchangeName"` + Currency string `json:"currency"` + AssetType string `json:"assetType"` } var WebsocketClientHub []WebsocketClient @@ -243,7 +245,7 @@ func WebsocketHandler() { } data, err := GetSpecificTicker(tickerReq.Currency, - tickerReq.Exchange) + tickerReq.Exchange, tickerReq.AssetType) if err != nil { wsResp.Error = err.Error() From 87633c21425792a80846f8352d4625ecebd6d195 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Sat, 2 Sep 2017 16:31:08 +1000 Subject: [PATCH 15/32] Link up orderbook websocket code, improve exchange test coverage and various other fixes --- common/common_test.go | 37 +- currency/currency.go | 33 +- currency/currency_test.go | 98 +++-- currency/pair/pair_test.go | 14 + exchanges/alphapoint/alphapoint_wrapper.go | 30 +- exchanges/anx/anx_wrapper.go | 10 +- exchanges/bitfinex/bitfinex_wrapper.go | 18 +- exchanges/bitfinex/bitfinex_wrapper_test.go | 6 +- exchanges/bitstamp/bitstamp_wrapper.go | 18 +- exchanges/bittrex/bittrex_wrapper.go | 18 +- exchanges/btcc/btcc_wrapper.go | 18 +- exchanges/btce/btce_wrapper.go | 18 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 18 +- exchanges/coinut/coinut_wrapper.go | 18 +- exchanges/exchange.go | 4 +- exchanges/exchange_test.go | 389 +++++++++++++++--- exchanges/gdax/gdax_wrapper.go | 18 +- exchanges/gemini/gemini_wrapper.go | 18 +- exchanges/huobi/huobi_wrapper.go | 18 +- exchanges/itbit/itbit_wrapper.go | 18 +- exchanges/kraken/kraken_wrapper.go | 18 +- exchanges/lakebtc/lakebtc_wrapper.go | 18 +- exchanges/liqui/liqui_wrapper.go | 18 +- .../localbitcoins/localbitcoins_wrapper.go | 18 +- exchanges/okcoin/okcoin_wrapper.go | 18 +- exchanges/orderbook/orderbook.go | 106 +++-- exchanges/orderbook/orderbook_test.go | 266 ++++++++++++ exchanges/poloniex/poloniex_wrapper.go | 18 +- exchanges/ticker/ticker_test.go | 41 ++ main.go | 2 +- orderbook_routes.go | 141 +++++++ restful_router.go | 1 + routines.go | 84 ++-- websocket.go | 1 - 34 files changed, 1215 insertions(+), 354 deletions(-) create mode 100644 exchanges/orderbook/orderbook_test.go create mode 100644 orderbook_routes.go diff --git a/common/common_test.go b/common/common_test.go index e7328612..31b17a0c 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -209,6 +209,11 @@ func TestBase64Decode(t *testing.T) { expectedOutput, actualResult, err), ) } + + _, err = Base64Decode("-") + if err == nil { + t.Error("Test failed. Bad base64 string failed returned nil error") + } } func TestBase64Encode(t *testing.T) { @@ -226,7 +231,7 @@ func TestBase64Encode(t *testing.T) { func TestStringSliceDifference(t *testing.T) { t.Parallel() originalInputOne := []string{"hello"} - originalInputTwo := []string{"moto"} + originalInputTwo := []string{"hello", "moto"} expectedOutput := []string{"hello moto"} actualResult := StringSliceDifference(originalInputOne, originalInputTwo) if reflect.DeepEqual(expectedOutput, actualResult) { @@ -334,14 +339,17 @@ func TestReplaceString(t *testing.T) { func TestRoundFloat(t *testing.T) { t.Parallel() - originalInput := float64(1.4545445445) - precisionInput := 2 - expectedOutput := float64(1.45) - actualResult := RoundFloat(originalInput, precisionInput) - if expectedOutput != actualResult { - t.Error(fmt.Sprintf( - "Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult), - ) + // mapping of input vs expected result + testTable := map[float64]float64{ + 2.3232323: 2.32, + -2.3232323: -2.32, + } + for testInput, expectedOutput := range testTable { + actualOutput := RoundFloat(testInput, 2) + if actualOutput != expectedOutput { + t.Error(fmt.Sprintf("Test failed. RoundFloat Expected '%f'. Actual '%f'.", + expectedOutput, actualOutput)) + } } } @@ -650,6 +658,11 @@ func TestWriteFile(t *testing.T) { if err != nil { t.Errorf("Test failed. Common WriteFile error: %s", err) } + + err = WriteFile("", nil) + if err == nil { + t.Error("Test failed. Common WriteFile allowed bad path") + } } func TestRemoveFile(t *testing.T) { @@ -672,9 +685,9 @@ func TestGetURIPath(t *testing.T) { t.Parallel() // mapping of input vs expected result testTable := map[string]string{ - "https://api.gdax.com/accounts": "/accounts", - "https://api.gdax.com/accounts?a=1&b=2": "/accounts?a=1&b=2", - "ht:tp:/invalidurl": "", + "https://api.gdax.com/accounts": "/accounts", + "https://api.gdax.com/accounts?a=1&b=2": "/accounts?a=1&b=2", + "http://www.google.com/accounts?!@#$%;^^": "", } for testInput, expectedOutput := range testTable { actualOutput := GetURIPath(testInput) diff --git a/currency/currency.go b/currency/currency.go index 32cfe22d..e29d8d7c 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -50,7 +50,6 @@ const ( maxCurrencyPairsPerRequest = 350 yahooYQLURL = "https://query.yahooapis.com/v1/public/yql?" yahooDatabase = "store://datatables.org/alltableswithkeys" - yahooEnabled = false fixerAPI = "http://api.fixer.io/latest" // DefaultCurrencies has the default minimum of FIAT values DefaultCurrencies = "USD,AUD,EUR,CNY" @@ -69,6 +68,7 @@ var ( ErrCurrencyNotFound = errors.New("unable to find specified currency") ErrQueryingYahoo = errors.New("unable to query Yahoo currency values") ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data") + yahooEnabled = false ) // IsDefaultCurrency checks if the currency passed in matches the default @@ -91,6 +91,7 @@ func IsDefaultCryptocurrency(currency string) bool { func IsFiatCurrency(currency string) bool { if BaseCurrencies == "" { log.Println("IsFiatCurrency: BaseCurrencies string variable not populated") + return false } return common.StringContains(BaseCurrencies, common.StringToUpper(currency)) } @@ -101,6 +102,7 @@ func IsCryptocurrency(currency string) bool { log.Println( "IsCryptocurrency: CryptoCurrencies string variable not populated", ) + return false } return common.StringContains(CryptoCurrencies, common.StringToUpper(currency)) } @@ -313,6 +315,8 @@ func FetchYahooCurrencyData(currencyPairs []string) error { return err } + log.Printf("Currency recv: %s", resp) + yahooResp := YahooJSONResponse{} err = common.JSONDecode([]byte(resp), &yahooResp) if err != nil { @@ -338,30 +342,5 @@ func QueryYahooCurrencyValues(currencies string) error { "%d fiat currency pairs generated. Fetching Yahoo currency data (this may take a minute)..\n", len(currencyPairs), ) - var err error - var pairs []string - index := 0 - - if len(currencyPairs) > maxCurrencyPairsPerRequest { - for index < len(currencyPairs) { - if len(currencyPairs)-index > maxCurrencyPairsPerRequest { - pairs = currencyPairs[index : index+maxCurrencyPairsPerRequest] - index += maxCurrencyPairsPerRequest - } else { - pairs = currencyPairs[index:] - index += (len(currencyPairs) - index) - } - err = FetchYahooCurrencyData(pairs) - if err != nil { - return err - } - } - } else { - pairs = currencyPairs[index:] - err = FetchYahooCurrencyData(pairs) - if err != nil { - return err - } - } - return nil + return FetchYahooCurrencyData(currencyPairs) } diff --git a/currency/currency_test.go b/currency/currency_test.go index 17a081d1..e7a0ad00 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -58,6 +58,10 @@ func TestIsDefaultCryptocurrency(t *testing.T) { func TestIsFiatCurrency(t *testing.T) { t.Parallel() + if IsFiatCurrency("") { + t.Error("Test failed. TestIsFiatCurrency returned true on an empty string") + } + BaseCurrencies = "USD,AUD" var str1, str2, str3 string = "BTC", "USD", "birds123" @@ -81,6 +85,10 @@ func TestIsFiatCurrency(t *testing.T) { func TestIsCryptocurrency(t *testing.T) { t.Parallel() + if IsCryptocurrency("") { + t.Error("Test failed. TestIsCryptocurrency returned true on an empty string") + } + CryptoCurrencies = "BTC,LTC,DASH" var str1, str2, str3 string = "USD", "BTC", "pterodactyl123" @@ -256,25 +264,25 @@ func TestCheckAndAddCurrency(t *testing.T) { } func TestSeedCurrencyData(t *testing.T) { - currencyRequestDefault := "" - currencyRequestUSDAUD := "USD,AUD" - currencyRequestObtuse := "WigWham" - - err := SeedCurrencyData(currencyRequestDefault) - if err != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err, currencyRequestDefault, - ) - } - err2 := SeedCurrencyData(currencyRequestUSDAUD) - if err2 != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err2, currencyRequestUSDAUD, - ) - } if yahooEnabled { + currencyRequestDefault := "" + currencyRequestUSDAUD := "USD,AUD" + currencyRequestObtuse := "WigWham" + + err := SeedCurrencyData(currencyRequestDefault) + if err != nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err, currencyRequestDefault, + ) + } + err2 := SeedCurrencyData(currencyRequestUSDAUD) + if err2 != nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err2, currencyRequestUSDAUD, + ) + } err3 := SeedCurrencyData(currencyRequestObtuse) if err3 == nil { t.Errorf( @@ -283,6 +291,12 @@ func TestSeedCurrencyData(t *testing.T) { ) } } + + yahooEnabled = false + err := SeedCurrencyData("") + if err != nil { + t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err) + } } func TestMakecurrencyPairs(t *testing.T) { @@ -299,12 +313,10 @@ func TestMakecurrencyPairs(t *testing.T) { } func TestConvertCurrency(t *testing.T) { - fiatCurrencies := DefaultCurrencies - for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { - for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { - if currencyFrom == currencyTo { - continue - } else { + if yahooEnabled { + fiatCurrencies := DefaultCurrencies + for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { + for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo) if err != nil { t.Errorf( @@ -323,6 +335,44 @@ func TestConvertCurrency(t *testing.T) { } } } + + yahooEnabled = false + _, err := ConvertCurrency(1000, "USD", "AUD") + if err != nil { + t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) + } + + _, err = ConvertCurrency(1000, "AUD", "USD") + if err != nil { + t.Errorf("Test failed. ConvertCurrency AUD -> AUD. Error %s", err) + } + + _, err = ConvertCurrency(1000, "CNY", "AUD") + if err != nil { + t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) + } + + // Test non-existant currencies + + _, err = ConvertCurrency(1000, "ASDF", "USD") + if err == nil { + t.Errorf("Test failed. ConvertCurrency non-existant currency -> USD. Error %s", err) + } + + _, err = ConvertCurrency(1000, "USD", "ASDF") + if err == nil { + t.Errorf("Test failed. ConvertCurrency USD -> non-existant currency. Error %s", err) + } + + _, err = ConvertCurrency(1000, "CNY", "UAHF") + if err == nil { + t.Errorf("Test failed. ConvertCurrency non-USD currency CNY -> non-existant currency. Error %s", err) + } + + _, err = ConvertCurrency(1000, "UASF", "UAHF") + if err == nil { + t.Errorf("Test failed. ConvertCurrency non-existant currency -> non-existant currency. Error %s", err) + } } func TestFetchFixerCurrencyData(t *testing.T) { diff --git a/currency/pair/pair_test.go b/currency/pair/pair_test.go index e787bba2..736abd01 100644 --- a/currency/pair/pair_test.go +++ b/currency/pair/pair_test.go @@ -158,6 +158,20 @@ func TestNewCurrencyPairFromIndex(t *testing.T) { actual, expected, ) } + + currency = "DOGEBTC" + + pair = NewCurrencyPairFromIndex(currency, index) + pair.Delimiter = "-" + actual = pair.Pair() + + expected = CurrencyItem("DOGE-BTC") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } } func TestNewCurrencyPairFromString(t *testing.T) { diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index b93b0968..8ccbdfab 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -29,7 +29,7 @@ func (a *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { } // UpdateTicker updates and returns the ticker for a currency pair -func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.Price, error) { +func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { var tickerPrice ticker.Price tick, err := a.GetTicker(p.Pair().String()) if err != nil { @@ -43,22 +43,22 @@ func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair) (ticker.Price, error) { tickerPrice.High = tick.High tickerPrice.Volume = tick.Volume tickerPrice.Last = tick.Last - ticker.ProcessTicker(a.GetName(), p, tickerPrice, ticker.Spot) - return ticker.GetTicker(a.Name, p, ticker.Spot) + ticker.ProcessTicker(a.GetName(), p, tickerPrice, assetType) + return ticker.GetTicker(a.Name, p, assetType) } // GetTickerPrice returns the ticker for a currency pair -func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) (ticker.Price, error) { - tick, err := ticker.GetTicker(a.GetName(), p, ticker.Spot) +func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(a.GetName(), p, assetType) if err != nil { - return a.UpdateTicker(p) + return a.UpdateTicker(p, assetType) } return tick, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (a *Alphapoint) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (a *Alphapoint) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := a.GetOrderbook(p.Pair().String()) if err != nil { return orderBook, err @@ -66,23 +66,23 @@ func (a *Alphapoint) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBa for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Quantity, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Quantity, Price: data.Price}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Quantity, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Quantity, Price: data.Price}) } - orderbook.ProcessOrderbook(a.GetName(), p, orderBook) - return orderbook.GetOrderbook(a.Name, p) + orderbook.ProcessOrderbook(a.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(a.Name, p, assetType) } // GetOrderbookEx returns the orderbook for a currency pair -func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(a.GetName(), p) +func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(a.GetName(), p, assetType) if err == nil { - return a.UpdateOrderbook(p) + return a.UpdateOrderbook(p, assetType) } return ob, nil } diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 8c6e9c8c..cf70d97b 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -100,17 +100,17 @@ func (a *ANX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pric } // GetOrderbookEx returns the orderbook for a currency pair -func (a *ANX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(a.GetName(), p) +func (a *ANX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(a.GetName(), p, assetType) if err == nil { - return a.UpdateOrderbook(p) + return a.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (a *ANX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (a *ANX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base return orderBook, nil } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 8a817e52..a2033f31 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -67,32 +67,32 @@ func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker } // GetOrderbookEx returns the orderbook for a currency pair -func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p) +func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) if err == nil { - return b.UpdateOrderbook(p) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.Pair().String(), nil) if err != nil { return orderBook, err } for x := range orderbookNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: orderbookNew.Asks[x].Price, Amount: orderbookNew.Asks[x].Amount}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: orderbookNew.Asks[x].Price, Amount: orderbookNew.Asks[x].Amount}) } for x := range orderbookNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Amount}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Amount}) } - orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderbook.GetOrderbook(b.Name, p) + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies on the diff --git a/exchanges/bitfinex/bitfinex_wrapper_test.go b/exchanges/bitfinex/bitfinex_wrapper_test.go index 4b614392..a565c0ba 100644 --- a/exchanges/bitfinex/bitfinex_wrapper_test.go +++ b/exchanges/bitfinex/bitfinex_wrapper_test.go @@ -19,7 +19,8 @@ func TestRun(t *testing.T) { func TestGetTickerPrice(t *testing.T) { getTickerPrice := Bitfinex{} - _, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD"), ticker.Spot) + _, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD"), + ticker.Spot) if err != nil { t.Errorf("Test Failed - Bitfinex GetTickerPrice() error: %s", err) } @@ -27,7 +28,8 @@ func TestGetTickerPrice(t *testing.T) { func TestGetOrderbookEx(t *testing.T) { getOrderBookEx := Bitfinex{} - _, err := getOrderBookEx.GetOrderbookEx(pair.NewCurrencyPair("BTC", "USD")) + _, err := getOrderBookEx.GetOrderbookEx(pair.NewCurrencyPair("BTC", "USD"), + ticker.Spot) if err != nil { t.Errorf("Test Failed - Bitfinex GetOrderbookEx() error: %s", err) } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 31c44b53..b8183e99 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -57,17 +57,17 @@ func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker } // GetOrderbookEx returns the orderbook for a currency pair -func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p) +func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) if err == nil { - return b.UpdateOrderbook(p) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.Pair().String()) if err != nil { return orderBook, err @@ -75,16 +75,16 @@ func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price}) } - orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderbook.GetOrderbook(b.Name, p) + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 00392a32..843cc733 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -100,17 +100,17 @@ func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker. } // GetOrderbookEx returns the orderbook for a currency pair -func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p) +func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) if err == nil { - return b.UpdateOrderbook(p) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(exchange.FormatExchangeCurrency(b.GetName(), p).String()) if err != nil { return orderBook, err @@ -118,7 +118,7 @@ func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, for x := range orderbookNew.Buy { orderBook.Bids = append(orderBook.Bids, - orderbook.OrderbookItem{ + orderbook.Item{ Amount: orderbookNew.Buy[x].Quantity, Price: orderbookNew.Buy[x].Rate, }, @@ -127,13 +127,13 @@ func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, for x := range orderbookNew.Sell { orderBook.Asks = append(orderBook.Asks, - orderbook.OrderbookItem{ + orderbook.Item{ Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Rate, }, ) } - orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderbook.GetOrderbook(b.Name, p) + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) } diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index bbab1953..a3dcf97b 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -56,17 +56,17 @@ func (b *BTCC) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pri } // GetOrderbookEx returns the orderbook for a currency pair -func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p) +func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) if err == nil { - return b.UpdateOrderbook(p) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (b *BTCC) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := b.GetOrderBook(exchange.FormatExchangeCurrency(b.GetName(), p).String(), 100) if err != nil { return orderBook, err @@ -74,16 +74,16 @@ func (b *BTCC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, er for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: data[0], Amount: data[1]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]}) } - orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderbook.GetOrderbook(b.Name, p) + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) } // GetExchangeAccountInfo : Retrieves balances for all enabled currencies for diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index 0b8e3eed..ff180858 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -85,17 +85,17 @@ func (b *BTCE) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pri } // GetOrderbookEx returns the orderbook for a currency pair -func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p) +func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) if err == nil { - return b.UpdateOrderbook(p) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCE) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (b *BTCE) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := b.GetDepth(exchange.FormatExchangeCurrency(b.Name, p).String()) if err != nil { return orderBook, err @@ -103,16 +103,16 @@ func (b *BTCE) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, er for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: data[0], Amount: data[1]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]}) } - orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderbook.GetOrderbook(b.Name, p) + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 841b316b..d9fde312 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -75,17 +75,17 @@ func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair, assetType string) (tick } // GetOrderbookEx returns orderbook base on the currency pair -func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p) +func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) if err == nil { - return b.UpdateOrderbook(p) + return b.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := b.GetOrderbook(p.GetFirstCurrency().String()) if err != nil { return orderBook, err @@ -93,16 +93,16 @@ func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBa for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) } - orderbook.ProcessOrderbook(b.GetName(), p, orderBook) - return orderbook.GetOrderbook(b.Name, p) + orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(b.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index bb60982e..0035e049 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -96,30 +96,30 @@ func (c *COINUT) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.P } // GetOrderbookEx returns orderbook base on the currency pair -func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(c.GetName(), p) +func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(c.GetName(), p, assetType) if err == nil { - return c.UpdateOrderbook(p) + return c.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (c *COINUT) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (c *COINUT) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.Pair().String()], 200) if err != nil { return orderBook, err } for x := range orderbookNew.Buy { - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Buy[x].Quantity, Price: orderbookNew.Buy[x].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Buy[x].Quantity, Price: orderbookNew.Buy[x].Price}) } for x := range orderbookNew.Sell { - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price}) } - orderbook.ProcessOrderbook(c.GetName(), p, orderBook) - return orderbook.GetOrderbook(c.Name, p) + orderbook.ProcessOrderbook(c.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(c.Name, p, assetType) } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 89c859e2..f66e382b 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -66,8 +66,8 @@ type IBotExchange interface { IsEnabled() bool GetTickerPrice(currency pair.CurrencyPair, assetType string) (ticker.Price, error) UpdateTicker(currency pair.CurrencyPair, assetType string) (ticker.Price, error) - GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) - UpdateOrderbook(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) + GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) + UpdateOrderbook(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) GetEnabledCurrencies() []pair.CurrencyPair GetExchangeAccountInfo() (AccountInfo, error) GetAuthenticatedAPISupport() bool diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 37039d60..2e5e71d1 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -3,10 +3,157 @@ package exchange import ( "testing" + "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +func TestSetAssetTypes(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Test failed. TestSetAssetTypes failed to load config file. Error: %s", err) + } + + b := Base{ + Name: "TESTNAME", + } + + err = b.SetAssetTypes() + if err == nil { + t.Fatal("Test failed. TestSetAssetTypes returned nil error for a non-existant exchange") + } + + b.Name = "ANX" + err = b.SetAssetTypes() + if err != nil { + t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err) + } + + exch, err := cfg.GetExchangeConfig(b.Name) + if err != nil { + t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err) + } + + exch.AssetTypes = "" + err = cfg.UpdateExchangeConfig(exch) + if err != nil { + t.Fatalf("Test failed. TestSetAssetTypes update config failed. Error %s", err) + } + + exch, err = cfg.GetExchangeConfig(b.Name) + if err != nil { + t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err) + } + + if exch.AssetTypes != "" { + t.Fatal("Test failed. TestSetAssetTypes assetTypes != ''") + } + + err = b.SetAssetTypes() + if err != nil { + t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err) + } + + if !common.DataContains(b.AssetTypes, ticker.Spot) { + t.Fatal("Test failed. TestSetAssetTypes assetTypes is not set") + } +} + +func TestGetExchangeAssetTypes(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Failed to load config file. Error: %s", err) + } + + result, err := GetExchangeAssetTypes("Bitfinex") + if err != nil { + t.Fatal("Test failed. Unable to obtain Bitfinex asset types") + } + + if !common.DataContains(result, ticker.Spot) { + t.Fatal("Test failed. Bitfinex does not contain default asset type 'SPOT'") + } + + _, err = GetExchangeAssetTypes("non-existant-exchange") + if err == nil { + t.Fatal("Test failed. Got asset types for non-existant exchange") + } +} + +func TestSetCurrencyPairFormat(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Test failed. TestSetCurrencyPairFormat failed to load config file. Error: %s", err) + } + + b := Base{ + Name: "TESTNAME", + } + + err = b.SetCurrencyPairFormat() + if err == nil { + t.Fatal("Test failed. TestSetCurrencyPairFormat returned nil error for a non-existant exchange") + } + + b.Name = "ANX" + err = b.SetCurrencyPairFormat() + if err != nil { + t.Fatalf("Test failed. TestSetCurrencyPairFormat. Error %s", err) + } + + exch, err := cfg.GetExchangeConfig(b.Name) + if err != nil { + t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err) + } + + exch.ConfigCurrencyPairFormat = nil + exch.RequestCurrencyPairFormat = nil + err = cfg.UpdateExchangeConfig(exch) + if err != nil { + t.Fatalf("Test failed. TestSetCurrencyPairFormat update config failed. Error %s", err) + } + + exch, err = cfg.GetExchangeConfig(b.Name) + if err != nil { + t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err) + } + + if exch.ConfigCurrencyPairFormat != nil && exch.RequestCurrencyPairFormat != nil { + t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil") + } + + err = b.SetCurrencyPairFormat() + if err != nil { + t.Fatalf("Test failed. TestSetCurrencyPairFormat. Error %s", err) + } + + if b.ConfigCurrencyPairFormat.Delimiter != "" && + b.ConfigCurrencyPairFormat.Index != "BTC" && + b.ConfigCurrencyPairFormat.Uppercase { + t.Fatal("Test failed. TestSetCurrencyPairFormat ConfigCurrencyPairFormat values are incorrect") + } + + if b.RequestCurrencyPairFormat.Delimiter != "" && + b.RequestCurrencyPairFormat.Index != "BTC" && + b.RequestCurrencyPairFormat.Uppercase { + t.Fatal("Test failed. TestSetCurrencyPairFormat RequestCurrencyPairFormat values are incorrect") + } +} + +func TestGetAuthenticatedAPISupport(t *testing.T) { + base := Base{ + AuthenticatedAPISupport: false, + } + + if base.GetAuthenticatedAPISupport() { + t.Fatal("Test failed. TestGetAuthenticatedAPISupport returned true when it should of been false.") + } +} + func TestGetName(t *testing.T) { GetName := Base{ Name: "TESTNAME", @@ -18,51 +165,132 @@ func TestGetName(t *testing.T) { } } -func TestSetCurrencyPairFormat(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) - if err != nil { - t.Fatalf("Failed to load config file. Error: %s", err) - } - - exch, err := cfg.GetExchangeConfig("GDAX") - if err != nil { - t.Fatalf("Failed to load GDAX exchange config. Error: %s", err) - } - - exch.RequestCurrencyPairFormat = nil - exch.ConfigCurrencyPairFormat = nil - - err = cfg.UpdateExchangeConfig(exch) - if err != nil { - t.Fatalf("Failed to update GDAX config. Error: %s", err) - } - - // to-do -} - func TestGetEnabledCurrencies(t *testing.T) { - enabledPairs := []string{"BTCUSD", "BTCAUD", "LTCUSD", "LTCAUD"} - GetEnabledCurrencies := Base{ - Name: "TESTNAME", - EnabledPairs: enabledPairs, + b := Base{ + Name: "TESTNAME", } - enCurr := GetEnabledCurrencies.GetEnabledCurrencies() - if enCurr[0].Pair().String() != "BTCUSD" { - t.Error("Test Failed - Exchange GetEnabledCurrencies() incorrect string") + b.EnabledPairs = []string{"BTC-USD"} + format := config.CurrencyPairFormatConfig{ + Delimiter: "-", + Index: "", + } + + b.RequestCurrencyPairFormat = format + b.ConfigCurrencyPairFormat = format + c := b.GetEnabledCurrencies() + if c[0].Pair().String() != "BTC-USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + format.Delimiter = "~" + b.RequestCurrencyPairFormat = format + c = b.GetEnabledCurrencies() + if c[0].Pair().String() != "BTC-USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + format.Delimiter = "" + b.ConfigCurrencyPairFormat = format + c = b.GetEnabledCurrencies() + if c[0].Pair().String() != "BTC-USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.EnabledPairs = []string{"BTCDOGE"} + format.Index = "BTC" + b.ConfigCurrencyPairFormat = format + c = b.GetEnabledCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.EnabledPairs = []string{"BTC_USD"} + b.RequestCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Delimiter = "_" + c = b.GetEnabledCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.EnabledPairs = []string{"BTCDOGE"} + b.RequestCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Index = "BTC" + c = b.GetEnabledCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.EnabledPairs = []string{"BTCUSD"} + b.ConfigCurrencyPairFormat.Index = "" + c = b.GetEnabledCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") } } func TestGetAvailableCurrencies(t *testing.T) { - availablePairs := []string{"BTCUSD", "BTCAUD", "LTCUSD", "LTCAUD"} - GetEnabledCurrencies := Base{ - Name: "TESTNAME", - AvailablePairs: availablePairs, + b := Base{ + Name: "TESTNAME", } - enCurr := GetEnabledCurrencies.GetAvailableCurrencies() - if enCurr[0].Pair().String() != "BTCUSD" { + b.AvailablePairs = []string{"BTC-USD"} + format := config.CurrencyPairFormatConfig{ + Delimiter: "-", + Index: "", + } + + b.RequestCurrencyPairFormat = format + b.ConfigCurrencyPairFormat = format + c := b.GetAvailableCurrencies() + if c[0].Pair().String() != "BTC-USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + format.Delimiter = "~" + b.RequestCurrencyPairFormat = format + c = b.GetAvailableCurrencies() + if c[0].Pair().String() != "BTC-USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + format.Delimiter = "" + b.ConfigCurrencyPairFormat = format + c = b.GetAvailableCurrencies() + if c[0].Pair().String() != "BTC-USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.AvailablePairs = []string{"BTCDOGE"} + format.Index = "BTC" + b.ConfigCurrencyPairFormat = format + c = b.GetAvailableCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.AvailablePairs = []string{"BTC_USD"} + b.RequestCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Delimiter = "_" + c = b.GetAvailableCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.AvailablePairs = []string{"BTCDOGE"} + b.RequestCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Delimiter = "" + b.ConfigCurrencyPairFormat.Index = "BTC" + c = b.GetAvailableCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } + + b.AvailablePairs = []string{"BTCUSD"} + b.ConfigCurrencyPairFormat.Index = "" + c = b.GetAvailableCurrencies() + if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" { t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") } } @@ -89,6 +317,14 @@ func TestGetExchangeFormatCurrencySeperator(t *testing.T) { t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v", expected, actual) } + + expected = false + actual = GetExchangeFormatCurrencySeperator("blah") + + if expected != actual { + t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v", + expected, actual) + } } func TestGetAndFormatExchangeCurrencies(t *testing.T) { @@ -112,6 +348,11 @@ func TestGetAndFormatExchangeCurrencies(t *testing.T) { t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies %s != %s", actual, expected) } + + _, err = GetAndFormatExchangeCurrencies("non-existant", pairs) + if err == nil { + t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies returned nil error on non-existant exchange") + } } func TestFormatExchangeCurrency(t *testing.T) { @@ -193,33 +434,77 @@ func TestSetAPIKeys(t *testing.T) { func TestUpdateEnabledCurrencies(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) - UAC := Base{Name: "ANX"} - enabledCurrencies := []string{"ltc", "btc", "usd", "aud"} - if err != nil { - t.Error( - "Test Failed - Exchange UpdateEnabledCurrencies() did not set correct values", - ) + t.Fatal("Test failed. TestUpdateEnabledCurrencies failed to load config") } - err2 := UAC.UpdateEnabledCurrencies(enabledCurrencies, false) - if err2 != nil { - t.Errorf("Test Failed - Exchange UpdateEnabledCurrencies() error: %s", err2) + + UAC := Base{Name: "ANX"} + exchangeProducts := []string{"ltc", "btc", "usd", "aud"} + + // Test updating exchange products for an exchange which doesn't exist + UAC.Name = "Blah" + err = UAC.UpdateEnabledCurrencies(exchangeProducts, false) + if err == nil { + t.Errorf("Test Failed - Exchange TestUpdateEnabledCurrencies succeeded on an exchange which doesn't exist") + } + + // Test updating exchange products + UAC.Name = "ANX" + err = UAC.UpdateEnabledCurrencies(exchangeProducts, false) + if err != nil { + t.Errorf("Test Failed - Exchange TestUpdateEnabledCurrencies error: %s", err) + } + + // Test updating the same new products, diff should be 0 + UAC.Name = "ANX" + err = UAC.UpdateEnabledCurrencies(exchangeProducts, false) + if err != nil { + t.Errorf("Test Failed - Exchange TestUpdateEnabledCurrencies error: %s", err) + } + + // Test force updating to only one product + exchangeProducts = []string{"btc"} + err = UAC.UpdateEnabledCurrencies(exchangeProducts, true) + if err != nil { + t.Errorf("Test Failed - Forced Exchange TestUpdateEnabledCurrencies error: %s", err) } } func TestUpdateAvailableCurrencies(t *testing.T) { cfg := config.GetConfig() err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatal("Test failed. TestUpdateAvailableCurrencies failed to load config") + } + UAC := Base{Name: "ANX"} exchangeProducts := []string{"ltc", "btc", "usd", "aud"} - if err != nil { - t.Error( - "Test Failed - Exchange UpdateAvailableCurrencies() did not set correct values", - ) + // Test updating exchange products for an exchange which doesn't exist + UAC.Name = "Blah" + err = UAC.UpdateAvailableCurrencies(exchangeProducts, false) + if err == nil { + t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() succeeded on an exchange which doesn't exist") } - err2 := UAC.UpdateAvailableCurrencies(exchangeProducts, false) - if err2 != nil { - t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err2) + + // Test updating exchange products + UAC.Name = "ANX" + err = UAC.UpdateAvailableCurrencies(exchangeProducts, false) + if err != nil { + t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err) + } + + // Test updating the same new products, diff should be 0 + UAC.Name = "ANX" + err = UAC.UpdateAvailableCurrencies(exchangeProducts, false) + if err != nil { + t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err) + } + + // Test force updating to only one product + exchangeProducts = []string{"btc"} + err = UAC.UpdateAvailableCurrencies(exchangeProducts, true) + if err != nil { + t.Errorf("Test Failed - Forced Exchange UpdateAvailableCurrencies() error: %s", err) } } diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index f6705edc..8ad7b404 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -97,17 +97,17 @@ func (g *GDAX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pri } // GetOrderbookEx returns orderbook base on the currency pair -func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(g.GetName(), p) +func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(g.GetName(), p, assetType) if err == nil { - return g.UpdateOrderbook(p) + return g.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := g.GetOrderbook(exchange.FormatExchangeCurrency(g.Name, p).String(), 2) if err != nil { return orderBook, err @@ -116,13 +116,13 @@ func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, er obNew := orderbookNew.(GDAXOrderbookL1L2) for x := range obNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } for x := range obNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } - orderbook.ProcessOrderbook(g.GetName(), p, orderBook) - return orderbook.GetOrderbook(g.Name, p) + orderbook.ProcessOrderbook(g.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(g.Name, p, assetType) } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 19ee46d9..143c8e88 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -78,30 +78,30 @@ func (g *Gemini) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.P } // GetOrderbookEx returns orderbook base on the currency pair -func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(g.GetName(), p) +func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(g.GetName(), p, assetType) if err == nil { - return g.UpdateOrderbook(p) + return g.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (g *Gemini) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (g *Gemini) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := g.GetOrderbook(p.Pair().String(), url.Values{}) if err != nil { return orderBook, err } for x := range orderbookNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) } for x := range orderbookNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } - orderbook.ProcessOrderbook(g.GetName(), p, orderBook) - return orderbook.GetOrderbook(g.Name, p) + orderbook.ProcessOrderbook(g.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(g.Name, p, assetType) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 10f7d5a4..31042c6d 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -56,17 +56,17 @@ func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pr } // GetOrderbookEx returns orderbook base on the currency pair -func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(h.GetName(), p) +func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(h.GetName(), p, assetType) if err == nil { - return h.UpdateOrderbook(p) + return h.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := h.GetOrderBook(p.GetFirstCurrency().Lower().String()) if err != nil { return orderBook, err @@ -74,16 +74,16 @@ func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) } - orderbook.ProcessOrderbook(h.GetName(), p, orderBook) - return orderbook.GetOrderbook(h.Name, p) + orderbook.ProcessOrderbook(h.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(h.Name, p, assetType) } //GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 871e923e..30335616 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -53,17 +53,17 @@ func (i *ItBit) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pr } // GetOrderbookEx returns orderbook base on the currency pair -func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(i.GetName(), p) +func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(i.GetName(), p, assetType) if err == nil { - return i.UpdateOrderbook(p) + return i.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := i.GetOrderbook(exchange.FormatExchangeCurrency(i.Name, p).String()) if err != nil { @@ -80,7 +80,7 @@ func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e if err != nil { log.Println(err) } - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: amount, Price: price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: amount, Price: price}) } for x := range orderbookNew.Asks { @@ -93,11 +93,11 @@ func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e if err != nil { log.Println(err) } - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: amount, Price: price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: amount, Price: price}) } - orderbook.ProcessOrderbook(i.GetName(), p, orderBook) - return orderbook.GetOrderbook(i.Name, p) + orderbook.ProcessOrderbook(i.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(i.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index c880dafc..30410649 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -78,32 +78,32 @@ func (k *Kraken) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.P } // GetOrderbookEx returns orderbook base on the currency pair -func (k *Kraken) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(k.GetName(), p) +func (k *Kraken) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(k.GetName(), p, assetType) if err == nil { - return k.UpdateOrderbook(p) + return k.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (k *Kraken) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (k *Kraken) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := k.GetDepth(exchange.FormatExchangeCurrency(k.GetName(), p).String()) if err != nil { return orderBook, err } for x := range orderbookNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) } for x := range orderbookNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } - orderbook.ProcessOrderbook(k.GetName(), p, orderBook) - return orderbook.GetOrderbook(k.Name, p) + orderbook.ProcessOrderbook(k.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(k.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index df01c9fa..4066e2dc 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -56,32 +56,32 @@ func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker. } // GetOrderbookEx returns orderbook base on the currency pair -func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(l.GetName(), p) +func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(l.GetName(), p, assetType) if err == nil { - return l.UpdateOrderbook(p) + return l.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *LakeBTC) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (l *LakeBTC) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := l.GetOrderBook(p.Pair().String()) if err != nil { return orderBook, err } for x := range orderbookNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) } for x := range orderbookNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } - orderbook.ProcessOrderbook(l.GetName(), p, orderBook) - return orderbook.GetOrderbook(l.Name, p) + orderbook.ProcessOrderbook(l.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(l.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index ba934d99..8e22e8ba 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -75,17 +75,17 @@ func (l *Liqui) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Pr } // GetOrderbookEx returns orderbook base on the currency pair -func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(l.Name, p) +func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(l.Name, p, assetType) if err == nil { - return l.UpdateOrderbook(p) + return l.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := l.GetDepth(exchange.FormatExchangeCurrency(l.Name, p).String()) if err != nil { return orderBook, err @@ -93,16 +93,16 @@ func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, e for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) } - orderbook.ProcessOrderbook(l.Name, p, orderBook) - return orderbook.GetOrderbook(l.Name, p) + orderbook.ProcessOrderbook(l.Name, p, orderBook, assetType) + return orderbook.GetOrderbook(l.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 06685353..9cfc99e9 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -52,17 +52,17 @@ func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair, assetType string) (t } // GetOrderbookEx returns orderbook base on the currency pair -func (l *LocalBitcoins) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(l.GetName(), p) +func (l *LocalBitcoins) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(l.GetName(), p, assetType) if err == nil { - return l.UpdateOrderbook(p) + return l.UpdateOrderbook(p, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := l.GetOrderbook(p.GetSecondCurrency().String()) if err != nil { return orderBook, err @@ -70,16 +70,16 @@ func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair) (orderbook.Orderboo for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price}) } - orderbook.ProcessOrderbook(l.GetName(), p, orderBook) - return orderbook.GetOrderbook(l.Name, p) + orderbook.ProcessOrderbook(l.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(l.Name, p, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index a1a4807f..9b155bfc 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -74,17 +74,17 @@ func (o *OKCoin) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.P } // GetOrderbookEx returns orderbook base on the currency pair -func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(o.GetName(), currency) +func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(o.GetName(), currency, assetType) if err == nil { - return o.UpdateOrderbook(currency) + return o.UpdateOrderbook(currency, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := o.GetOrderBook(exchange.FormatExchangeCurrency(o.Name, currency).String(), 200, false) if err != nil { return orderBook, err @@ -92,16 +92,16 @@ func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair) (orderbook.Orderboo for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data[1], Price: data[0]}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]}) } - orderbook.ProcessOrderbook(o.GetName(), currency, orderBook) - return orderbook.GetOrderbook(o.Name, currency) + orderbook.ProcessOrderbook(o.GetName(), currency, orderBook, assetType) + return orderbook.GetOrderbook(o.Name, currency, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 50528dac..0236bc87 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -7,33 +7,44 @@ import ( "github.com/thrasher-/gocryptotrader/currency/pair" ) -var ( +// Const values for orderbook package +const ( ErrOrderbookForExchangeNotFound = "Ticker for exchange does not exist." ErrPrimaryCurrencyNotFound = "Error primary currency for orderbook not found." ErrSecondaryCurrencyNotFound = "Error secondary currency for orderbook not found." + Spot = "SPOT" +) + +// Vars for the orderbook package +var ( Orderbooks []Orderbook ) -type OrderbookItem struct { +// Item stores the amount and price values +type Item struct { Amount float64 Price float64 } -type OrderbookBase struct { +// Base holds the fields for the orderbook base +type Base struct { Pair pair.CurrencyPair `json:"pair"` CurrencyPair string `json:"CurrencyPair"` - Bids []OrderbookItem `json:"bids"` - Asks []OrderbookItem `json:"asks"` + Bids []Item `json:"bids"` + Asks []Item `json:"asks"` LastUpdated time.Time `json:"last_updated"` } +// Orderbook holds the orderbook information for a currency pair and type type Orderbook struct { - Orderbook map[pair.CurrencyItem]map[pair.CurrencyItem]OrderbookBase + Orderbook map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Base ExchangeName string } -func (o *OrderbookBase) CalculateTotalBids() (float64, float64) { +// CalculateTotalBids returns the total amount of bids and the total orderbook +// bids value +func (o *Base) CalculateTotalBids() (float64, float64) { amountCollated := float64(0) total := float64(0) for _, x := range o.Bids { @@ -43,7 +54,9 @@ func (o *OrderbookBase) CalculateTotalBids() (float64, float64) { return amountCollated, total } -func (o *OrderbookBase) CalculateTotalAsks() (float64, float64) { +// CalculateTotalAsks returns the total amount of asks and the total orderbook +// asks value +func (o *Base) CalculateTotalAsks() (float64, float64) { amountCollated := float64(0) total := float64(0) for _, x := range o.Asks { @@ -53,29 +66,33 @@ func (o *OrderbookBase) CalculateTotalAsks() (float64, float64) { return amountCollated, total } -func (o *OrderbookBase) Update(Bids, Asks []OrderbookItem) { +// Update updates the bids and asks +func (o *Base) Update(Bids, Asks []Item) { o.Bids = Bids o.Asks = Asks o.LastUpdated = time.Now() } -func GetOrderbook(exchange string, p pair.CurrencyPair) (OrderbookBase, error) { +// GetOrderbook checks and returns the orderbook given an exchange name and +// currency pair if it exists +func GetOrderbook(exchange string, p pair.CurrencyPair, orderbookType string) (Base, error) { orderbook, err := GetOrderbookByExchange(exchange) if err != nil { - return OrderbookBase{}, err + return Base{}, err } if !FirstCurrencyExists(exchange, p.GetFirstCurrency()) { - return OrderbookBase{}, errors.New(ErrPrimaryCurrencyNotFound) + return Base{}, errors.New(ErrPrimaryCurrencyNotFound) } if !SecondCurrencyExists(exchange, p) { - return OrderbookBase{}, errors.New(ErrSecondaryCurrencyNotFound) + return Base{}, errors.New(ErrSecondaryCurrencyNotFound) } - return orderbook.Orderbook[p.GetFirstCurrency()][p.GetSecondCurrency()], nil + return orderbook.Orderbook[p.GetFirstCurrency()][p.GetSecondCurrency()][orderbookType], nil } +// GetOrderbookByExchange returns an exchange orderbook func GetOrderbookByExchange(exchange string) (*Orderbook, error) { for _, y := range Orderbooks { if y.ExchangeName == exchange { @@ -85,6 +102,8 @@ func GetOrderbookByExchange(exchange string) (*Orderbook, error) { return nil, errors.New(ErrOrderbookForExchangeNotFound) } +// FirstCurrencyExists checks to see if the first currency of the orderbook map +// exists func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool { for _, y := range Orderbooks { if y.ExchangeName == exchange { @@ -96,6 +115,8 @@ func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool { return false } +// SecondCurrencyExists checks to see if the second currency of the orderbook +// map exists func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool { for _, y := range Orderbooks { if y.ExchangeName == exchange { @@ -109,40 +130,51 @@ func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool { return false } -func CreateNewOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew OrderbookBase) Orderbook { +// CreateNewOrderbook creates a new orderbook +func CreateNewOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew Base, orderbookType string) Orderbook { orderbook := Orderbook{} orderbook.ExchangeName = exchangeName - orderbook.Orderbook = make(map[pair.CurrencyItem]map[pair.CurrencyItem]OrderbookBase) - sMap := make(map[pair.CurrencyItem]OrderbookBase) - sMap[p.GetSecondCurrency()] = orderbookNew - orderbook.Orderbook[p.GetFirstCurrency()] = sMap + orderbook.Orderbook = make(map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Base) + a := make(map[pair.CurrencyItem]map[string]Base) + b := make(map[string]Base) + b[orderbookType] = orderbookNew + a[p.SecondCurrency] = b + orderbook.Orderbook[p.FirstCurrency] = a Orderbooks = append(Orderbooks, orderbook) return orderbook } -func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew OrderbookBase) { +// ProcessOrderbook processes incoming orderbooks, creating or updating the +// Orderbook list +func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew Base, orderbookType string) { orderbookNew.CurrencyPair = p.Pair().String() orderbookNew.LastUpdated = time.Now() + if len(Orderbooks) == 0 { - CreateNewOrderbook(exchangeName, p, orderbookNew) + CreateNewOrderbook(exchangeName, p, orderbookNew, orderbookType) return - } else { - orderbook, err := GetOrderbookByExchange(exchangeName) - if err != nil { - CreateNewOrderbook(exchangeName, p, orderbookNew) + } + + orderbook, err := GetOrderbookByExchange(exchangeName) + if err != nil { + CreateNewOrderbook(exchangeName, p, orderbookNew, orderbookType) + return + } + + if FirstCurrencyExists(exchangeName, p.GetFirstCurrency()) { + if !SecondCurrencyExists(exchangeName, p) { + a := orderbook.Orderbook[p.FirstCurrency] + b := make(map[string]Base) + b[orderbookType] = orderbookNew + a[p.SecondCurrency] = b + orderbook.Orderbook[p.FirstCurrency] = a return } - - if FirstCurrencyExists(exchangeName, p.GetFirstCurrency()) { - if !SecondCurrencyExists(exchangeName, p) { - second := orderbook.Orderbook[p.GetFirstCurrency()] - second[p.GetSecondCurrency()] = orderbookNew - orderbook.Orderbook[p.GetFirstCurrency()] = second - return - } - } - sMap := make(map[pair.CurrencyItem]OrderbookBase) - sMap[p.GetSecondCurrency()] = orderbookNew - orderbook.Orderbook[p.GetFirstCurrency()] = sMap } + + a := make(map[pair.CurrencyItem]map[string]Base) + b := make(map[string]Base) + b[orderbookType] = orderbookNew + a[p.SecondCurrency] = b + orderbook.Orderbook[p.FirstCurrency] = a } diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go new file mode 100644 index 00000000..d03e4c26 --- /dev/null +++ b/exchanges/orderbook/orderbook_test.go @@ -0,0 +1,266 @@ +package orderbook + +import ( + "testing" + "time" + + "github.com/thrasher-/gocryptotrader/currency/pair" +) + +func TestCalculateTotalBids(t *testing.T) { + t.Parallel() + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Bids: []Item{Item{Price: 100, Amount: 10}}, + LastUpdated: time.Now(), + } + + a, b := base.CalculateTotalBids() + if a != 10 && b != 1000 { + t.Fatal("Test failed. TestCalculateTotalBids expected a = 10 and b = 1000") + } +} + +func TestCalculateTotaAsks(t *testing.T) { + t.Parallel() + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + LastUpdated: time.Now(), + } + + a, b := base.CalculateTotalAsks() + if a != 10 && b != 1000 { + t.Fatal("Test failed. TestCalculateTotalAsks expected a = 10 and b = 1000") + } +} + +func TestUpdate(t *testing.T) { + t.Parallel() + currency := pair.NewCurrencyPair("BTC", "USD") + timeNow := time.Now() + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + LastUpdated: timeNow, + } + + asks := []Item{Item{Price: 200, Amount: 101}} + bids := []Item{Item{Price: 201, Amount: 100}} + time.Sleep(time.Millisecond * 50) + base.Update(bids, asks) + + if !base.LastUpdated.After(timeNow) { + t.Fatal("test failed. TestUpdate expected LastUpdated to be greater then original time") + } + + a, b := base.CalculateTotalAsks() + if a != 100 && b != 20200 { + t.Fatal("Test failed. TestUpdate expected a = 100 and b = 20100") + } + + a, b = base.CalculateTotalBids() + if a != 100 && b != 20100 { + t.Fatal("Test failed. TestUpdate expected a = 100 and b = 20100") + } +} + +func TestGetOrderbook(t *testing.T) { + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + } + + CreateNewOrderbook("Exchange", currency, base, Spot) + + result, err := GetOrderbook("Exchange", currency, Spot) + if err != nil { + t.Fatalf("Test failed. TestGetOrderbook failed to get orderbook. Error %s", + err) + } + + if result.Pair.Pair() != currency.Pair() { + t.Fatal("Test failed. TestGetOrderbook failed. Mismatched pairs") + } + + _, err = GetOrderbook("nonexistant", currency, Spot) + if err == nil { + t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook") + } + + currency.FirstCurrency = "blah" + _, err = GetOrderbook("Exchange", currency, Spot) + if err == nil { + t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook using invalid first currency") + } + + newCurrency := pair.NewCurrencyPair("BTC", "AUD") + _, err = GetOrderbook("Exchange", newCurrency, Spot) + if err == nil { + t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook using invalid second currency") + } +} + +func TestGetOrderbookByExchange(t *testing.T) { + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + } + + CreateNewOrderbook("Exchange", currency, base, Spot) + + _, err := GetOrderbookByExchange("Exchange") + if err != nil { + t.Fatalf("Test failed. TestGetOrderbookByExchange failed to get orderbook. Error %s", + err) + } + + _, err = GetOrderbookByExchange("nonexistant") + if err == nil { + t.Fatal("Test failed. TestGetOrderbookByExchange retrieved non-existant orderbook") + } +} + +func TestFirstCurrencyExists(t *testing.T) { + currency := pair.NewCurrencyPair("BTC", "AUD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + } + + CreateNewOrderbook("Exchange", currency, base, Spot) + + if !FirstCurrencyExists("Exchange", currency.FirstCurrency) { + t.Fatal("Test failed. TestFirstCurrencyExists expected first currency doesn't exist") + } + + var item pair.CurrencyItem = "blah" + if FirstCurrencyExists("Exchange", item) { + t.Fatal("Test failed. TestFirstCurrencyExists unexpected first currency exists") + } +} + +func TestSecondCurrencyExists(t *testing.T) { + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + } + + CreateNewOrderbook("Exchange", currency, base, Spot) + + if !SecondCurrencyExists("Exchange", currency) { + t.Fatal("Test failed. TestSecondCurrencyExists expected first currency doesn't exist") + } + + currency.SecondCurrency = "blah" + if SecondCurrencyExists("Exchange", currency) { + t.Fatal("Test failed. TestSecondCurrencyExists unexpected first currency exists") + } +} + +func TestCreateNewOrderbook(t *testing.T) { + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + } + + CreateNewOrderbook("Exchange", currency, base, Spot) + + result, err := GetOrderbook("Exchange", currency, Spot) + if err != nil { + t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook") + } + + if result.Pair.Pair() != currency.Pair() { + t.Fatal("Test failed. TestCreateNewOrderbook result pair is incorrect") + } + + a, b := result.CalculateTotalAsks() + if a != 10 && b != 1000 { + t.Fatal("Test failed. TestCreateNewOrderbook CalculateTotalAsks value is incorrect") + } + + a, b = result.CalculateTotalBids() + if a != 10 && b != 2000 { + t.Fatal("Test failed. TestCreateNewOrderbook CalculateTotalBids value is incorrect") + } +} + +func TestProcessOrderbook(t *testing.T) { + Orderbooks = []Orderbook{} + currency := pair.NewCurrencyPair("BTC", "USD") + base := Base{ + Pair: currency, + CurrencyPair: currency.Pair().String(), + Asks: []Item{Item{Price: 100, Amount: 10}}, + Bids: []Item{Item{Price: 200, Amount: 10}}, + } + + ProcessOrderbook("Exchange", currency, base, Spot) + + result, err := GetOrderbook("Exchange", currency, Spot) + if err != nil { + t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") + } + + if result.Pair.Pair() != currency.Pair() { + t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect") + } + + currency = pair.NewCurrencyPair("BTC", "GBP") + base.Pair = currency + ProcessOrderbook("Exchange", currency, base, Spot) + + result, err = GetOrderbook("Exchange", currency, Spot) + if err != nil { + t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") + } + + if result.Pair.Pair() != currency.Pair() { + t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect") + } + + base.Asks = []Item{Item{Price: 200, Amount: 200}} + ProcessOrderbook("Exchange", currency, base, "monthly") + + result, err = GetOrderbook("Exchange", currency, "monthly") + if err != nil { + t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook") + } + + a, b := result.CalculateTotalAsks() + if a != 200 && b != 40000 { + t.Fatal("Test failed. TestProcessOrderbook CalculateTotalsAsks incorrect values") + } + + base.Bids = []Item{Item{Price: 420, Amount: 200}} + ProcessOrderbook("Blah", currency, base, "quarterly") + result, err = GetOrderbook("Blah", currency, "quarterly") + if err != nil { + t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook") + } + + if a != 200 && b != 84000 { + t.Fatal("Test failed. TestProcessOrderbook CalculateTotalsBids incorrect values") + } +} diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 10896e50..78d7a480 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -61,17 +61,17 @@ func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair, assetType stri } // GetOrderbookEx returns orderbook base on the currency pair -func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) { - ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair) +func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair, assetType) if err == nil { - return p.UpdateOrderbook(currencyPair) + return p.UpdateOrderbook(currencyPair, assetType) } return ob, nil } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) { - var orderBook orderbook.OrderbookBase +func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base orderbookNew, err := p.GetOrderbook(exchange.FormatExchangeCurrency(p.GetName(), currencyPair).String(), 1000) if err != nil { return orderBook, err @@ -79,16 +79,16 @@ func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair) (orderbook.Or for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price}) } for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price}) } - orderbook.ProcessOrderbook(p.GetName(), currencyPair, orderBook) - return orderbook.GetOrderbook(p.Name, currencyPair) + orderbook.ProcessOrderbook(p.GetName(), currencyPair, orderBook, assetType) + return orderbook.GetOrderbook(p.Name, currencyPair, assetType) } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index eb7c8b91..7f5d29ed 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -72,6 +72,23 @@ func TestGetTicker(t *testing.T) { t.Error("Test Failed - ticker tickerPrice.CurrencyPair value is incorrect") } + _, err = GetTicker("blah", newPair, Spot) + if err == nil { + t.Fatal("Test Failed. TestGetTicker returned nil error on invalid exchange") + } + + newPair.FirstCurrency = "ETH" + _, err = GetTicker("bitfinex", newPair, Spot) + if err == nil { + t.Fatal("Test Failed. TestGetTicker returned ticker for invalid first currency") + } + + btcltcPair := pair.NewCurrencyPair("BTC", "LTC") + _, err = GetTicker("bitfinex", btcltcPair, Spot) + if err == nil { + t.Fatal("Test Failed. TestGetTicker returned ticker for invalid second currency") + } + priceStruct.PriceATH = 9001 ProcessTicker("bitfinex", newPair, priceStruct, "futures_3m") tickerPrice, err = GetTicker("bitfinex", newPair, "futures_3m") @@ -220,6 +237,7 @@ func TestCreateNewTicker(t *testing.T) { } func TestProcessTicker(t *testing.T) { //non-appending function to tickers + Tickers = []Ticker{} newPair := pair.NewCurrencyPair("BTC", "USD") priceStruct := Price{ Pair: newPair, @@ -234,4 +252,27 @@ func TestProcessTicker(t *testing.T) { //non-appending function to tickers } ProcessTicker("btcc", newPair, priceStruct, Spot) + + result, err := GetTicker("btcc", newPair, Spot) + if err != nil { + t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") + } + + if result.Pair.Pair() != newPair.Pair() { + t.Fatal("Test failed. TestProcessTicker pair mismatch") + } + + secondPair := pair.NewCurrencyPair("BTC", "AUD") + priceStruct.Pair = secondPair + ProcessTicker("btcc", secondPair, priceStruct, Spot) + + result, err = GetTicker("btcc", secondPair, Spot) + if err != nil { + t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker") + } + + result, err = GetTicker("btcc", newPair, Spot) + if err != nil { + t.Fatal("Test failed. TestProcessTicker failed to return an existing ticker") + } } diff --git a/main.go b/main.go index 97b9dbfd..08a6b9d8 100644 --- a/main.go +++ b/main.go @@ -191,7 +191,7 @@ func main() { go WebsocketHandler() go TickerUpdaterRoutine() - //go OrderbookUpdaterRoutine() + go OrderbookUpdaterRoutine() if bot.config.Webserver.Enabled { err := bot.config.CheckWebserverConfigValues() diff --git a/orderbook_routes.go b/orderbook_routes.go new file mode 100644 index 00000000..6a708739 --- /dev/null +++ b/orderbook_routes.go @@ -0,0 +1,141 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" +) + +// GetSpecificOrderbook returns a specific orderbook given the currency, +// exchangeName and assetType +func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) { + var specificOrderbook orderbook.Base + var err error + for i := 0; i < len(bot.exchanges); i++ { + if bot.exchanges[i] != nil { + if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { + specificOrderbook, err = bot.exchanges[i].GetOrderbookEx( + pair.NewCurrencyPairFromString(currency), + assetType, + ) + break + } + } + } + return specificOrderbook, err +} + +func jsonOrderbookResponse(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + currency := vars["currency"] + exchange := vars["exchangeName"] + assetType := vars["assetType"] + + if assetType == "" { + assetType = orderbook.Spot + } + + response, err := GetSpecificOrderbook(currency, exchange, assetType) + if err != nil { + log.Printf("Failed to fetch orderbook for %s currency: %s\n", exchange, + currency) + return + } + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + panic(err) + } +} + +// AllEnabledExchangeOrderbooks holds the enabled exchange orderbooks +type AllEnabledExchangeOrderbooks struct { + Data []EnabledExchangeOrderbooks `json:"data"` +} + +// EnabledExchangeOrderbooks is a sub type for singular exchanges and respective +// orderbooks +type EnabledExchangeOrderbooks struct { + ExchangeName string `json:"exchangeName"` + ExchangeValues []orderbook.Base `json:"exchangeValues"` +} + +// GetAllActiveOrderbooks returns all enabled exchanges orderbooks +func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks { + var orderbookData []EnabledExchangeOrderbooks + + for _, individualBot := range bot.exchanges { + if individualBot != nil && individualBot.IsEnabled() { + var individualExchange EnabledExchangeOrderbooks + exchangeName := individualBot.GetName() + individualExchange.ExchangeName = exchangeName + currencies := individualBot.GetEnabledCurrencies() + assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) + if err != nil { + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) + continue + } + for _, x := range currencies { + currency := x + + var ob orderbook.Base + if len(assetTypes) > 1 { + for y := range assetTypes { + ob, err = individualBot.UpdateOrderbook(currency, + assetTypes[y]) + } + } else { + ob, err = individualBot.UpdateOrderbook(currency, + assetTypes[0]) + } + + if err != nil { + log.Printf("failed to get %s %s orderbook. Error: %s", + currency.Pair().String(), + exchangeName, + err) + continue + } + + individualExchange.ExchangeValues = append( + individualExchange.ExchangeValues, ob, + ) + } + orderbookData = append(orderbookData, individualExchange) + } + } + return orderbookData +} + +func getAllActiveOrderbooksResponse(w http.ResponseWriter, r *http.Request) { + var response AllEnabledExchangeOrderbooks + response.Data = GetAllActiveOrderbooks() + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(response); err != nil { + panic(err) + } +} + +// OrderbookRoutes denotes the current exchange orderbook routes +var OrderbookRoutes = Routes{ + Route{ + "AllActiveExchangesAndOrderbooks", + "GET", + "/exchanges/orderbook/latest/all", + getAllActiveOrderbooksResponse, + }, + Route{ + "IndividualExchangeOrderbook", + "GET", + "/exchanges/{exchangeName}/orderbook/latest/{currency}", + jsonOrderbookResponse, + }, +} diff --git a/restful_router.go b/restful_router.go index 467dd05f..f151bb3d 100644 --- a/restful_router.go +++ b/restful_router.go @@ -18,6 +18,7 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { allRoutes = append(allRoutes, WalletRoutes...) allRoutes = append(allRoutes, IndexRoute...) allRoutes = append(allRoutes, WebsocketRoutes...) + allRoutes = append(allRoutes, OrderbookRoutes...) for _, route := range allRoutes { var handler http.Handler handler = route.HandlerFunc diff --git a/routines.go b/routines.go index 0da929fe..3a20b24b 100644 --- a/routines.go +++ b/routines.go @@ -5,6 +5,8 @@ import ( "log" "time" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -31,6 +33,31 @@ func printSummary(result ticker.Price, p pair.CurrencyPair, assetType, exchangeN result.Volume) } +func printOrderbookSummary(result orderbook.Base, p pair.CurrencyPair, assetType, exchangeName string, err error) { + if err != nil { + log.Printf("failed to get %s %s orderbook. Error: %s", + p.Pair().String(), + exchangeName, + err) + return + } + + bidsAmount, bidsValue := result.CalculateTotalBids() + asksAmount, asksValue := result.CalculateTotalAsks() + + log.Printf("%s %s %s: Orderbook Bids len: %d amount: %f total value: %f Asks len: %d amount: %f total value: %f", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + len(result.Bids), + bidsAmount, + bidsValue, + len(result.Asks), + asksAmount, + asksValue, + ) +} + func relayWebsocketEvent(result interface{}, event, assetType, exchangeName string) { evt := WebsocketEvent{ Data: result, @@ -57,13 +84,15 @@ func TickerUpdaterRoutine() { var err error var assetTypes []string + assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName) + if err != nil { + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) + } + for y := range enabledCurrencies { currency := enabledCurrencies[y] - assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Printf("failed to get %s exchange asset types. Error: %s", - exchangeName, err) - } + if len(assetTypes) > 1 { for z := range assetTypes { result, err = bot.exchanges[x].UpdateTicker(currency, @@ -93,33 +122,42 @@ func OrderbookUpdaterRoutine() { for { for x := range bot.exchanges { if bot.exchanges[x].IsEnabled() { - exchangeName := bot.exchanges[x].GetName() - - if exchangeName == "ANX" { + if bot.exchanges[x].GetName() == "ANX" { continue } + exchangeName := bot.exchanges[x].GetName() enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies() + var result orderbook.Base + var err error + var assetTypes []string + + assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName) + if err != nil { + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) + } for y := range enabledCurrencies { currency := enabledCurrencies[y] - result, err := bot.exchanges[x].UpdateOrderbook(currency) - if err != nil { - log.Printf("failed to get %s orderbook", currency.Pair().String()) - continue - } - log.Printf("%s %s %v", - exchangeName, - exchange.FormatCurrency(currency).String(), - result) - - evt := WebsocketEvent{ - Data: result, - Event: "orderbook_update", - Exchange: exchangeName, + if len(assetTypes) > 1 { + for z := range assetTypes { + result, err = bot.exchanges[x].UpdateOrderbook(currency, + assetTypes[z]) + printOrderbookSummary(result, currency, assetTypes[z], exchangeName, err) + if err == nil { + relayWebsocketEvent(result, "orderbook_update", assetTypes[z], exchangeName) + } + } + } else { + result, err = bot.exchanges[x].UpdateOrderbook(currency, + assetTypes[0]) + printOrderbookSummary(result, currency, assetTypes[0], exchangeName, err) + if err == nil { + relayWebsocketEvent(result, "orderbook_update", assetTypes[0], exchangeName) + } } - BroadcastWebsocketMessage(evt) } } } diff --git a/websocket.go b/websocket.go index 3086216a..8c697e57 100644 --- a/websocket.go +++ b/websocket.go @@ -94,7 +94,6 @@ func SendWebsocketMessage(id int, data interface{}) error { } func BroadcastWebsocketMessage(evt WebsocketEvent) error { - log.Println(evt) for _, x := range WebsocketClientHub { x.Conn.WriteJSON(evt) } From 69aa445a3a08b8fe344553f50c1cbac2e9a9cc1d Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 4 Sep 2017 11:31:13 +1000 Subject: [PATCH 16/32] Refactor stats and link up to ticker routine --- currency/pair/pair.go | 9 ++ currency/pair/pair_test.go | 24 +++++ exchanges/btce/btce_wrapper.go | 2 - exchanges/stats/stats.go | 141 +++++++++++++++-------------- exchanges/stats/stats_test.go | 156 +++++++++++++++++++-------------- routines.go | 5 +- 6 files changed, 204 insertions(+), 133 deletions(-) diff --git a/currency/pair/pair.go b/currency/pair/pair.go index 3b105ba0..a0ca789c 100644 --- a/currency/pair/pair.go +++ b/currency/pair/pair.go @@ -62,6 +62,15 @@ func (c CurrencyPair) Display(delimiter string, uppercase bool) CurrencyItem { return pair.Lower() } +// Equal compares two currency pairs and returns whether or not they are equal +func (c CurrencyPair) Equal(p CurrencyPair) bool { + if c.FirstCurrency.Upper() == p.FirstCurrency.Upper() && + c.SecondCurrency.Upper() == p.SecondCurrency.Upper() { + return true + } + return false +} + // NewCurrencyPairDelimiter splits the desired currency string at delimeter, // the returns a CurrencyPair struct func NewCurrencyPairDelimiter(currency, delimiter string) CurrencyPair { diff --git a/currency/pair/pair_test.go b/currency/pair/pair_test.go index 736abd01..c8f6fcd0 100644 --- a/currency/pair/pair_test.go +++ b/currency/pair/pair_test.go @@ -105,6 +105,30 @@ func TestDisplay(t *testing.T) { } } +func TestEqual(t *testing.T) { + t.Parallel() + pair := NewCurrencyPair("BTC", "USD") + secondPair := NewCurrencyPair("btc", "uSd") + actual := pair.Equal(secondPair) + expected := true + if actual != expected { + t.Errorf( + "Test failed. Equal(): %v was not equal to expected value: %v", + actual, expected, + ) + } + + secondPair.SecondCurrency = "ETH" + actual = pair.Equal(secondPair) + expected = false + if actual != expected { + t.Errorf( + "Test failed. Equal(): %v was not equal to expected value: %v", + actual, expected, + ) + } +} + func TestNewCurrencyPair(t *testing.T) { t.Parallel() pair := NewCurrencyPair("BTC", "USD") diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index ff180858..282f9a38 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -9,7 +9,6 @@ import ( "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -45,7 +44,6 @@ func (b *BTCE) Run() { x = common.StringToUpper(x[0:3] + x[4:]) log.Printf("BTC-e %s: Last %f High %f Low %f Volume %f\n", x, y.Last, y.High, y.Low, y.Vol_cur) b.Ticker[x] = y - stats.AddExchangeInfo(b.GetName(), common.StringToUpper(x[0:3]), common.StringToUpper(x[4:]), y.Last, y.Vol_cur) } }() time.Sleep(time.Second * b.RESTPollingDelay) diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index 5d477e62..0b003e2f 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -3,113 +3,124 @@ package stats import ( "sort" - "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/currency/pair" ) -type ExchangeInfo struct { - Exchange string - FirstCurrency string - FiatCurrency string - Price float64 - Volume float64 +// Item holds various fields for storing currency pair stats +type Item struct { + Exchange string + Pair pair.CurrencyPair + AssetType string + Price float64 + Volume float64 } -var ExchInfo []ExchangeInfo +// Items var array +var Items []Item -type ByPrice []ExchangeInfo +// ByPrice allows sorting by price +type ByPrice []Item -func (this ByPrice) Len() int { - return len(this) +func (b ByPrice) Len() int { + return len(b) } -func (this ByPrice) Less(i, j int) bool { - return this[i].Price < this[j].Price +func (b ByPrice) Less(i, j int) bool { + return b[i].Price < b[j].Price } -func (this ByPrice) Swap(i, j int) { - this[i], this[j] = this[j], this[i] +func (b ByPrice) Swap(i, j int) { + b[i], b[j] = b[j], b[i] } -type ByVolume []ExchangeInfo +// ByVolume allows sorting by volume +type ByVolume []Item -func (this ByVolume) Len() int { - return len(this) +func (b ByVolume) Len() int { + return len(b) } -func (this ByVolume) Less(i, j int) bool { - return this[i].Volume < this[j].Volume +func (b ByVolume) Less(i, j int) bool { + return b[i].Volume < b[j].Volume } -func (this ByVolume) Swap(i, j int) { - this[i], this[j] = this[j], this[i] +func (b ByVolume) Swap(i, j int) { + b[i], b[j] = b[j], b[i] } -func AddExchangeInfo(exchange, crypto, fiat string, price, volume float64) { - if currency.BaseCurrencies == "" { - currency.BaseCurrencies = currency.DefaultCurrencies - } - - if !currency.IsFiatCurrency(fiat) { - return - } - AppendExchangeInfo(exchange, crypto, fiat, price, volume) - -} - -func AppendExchangeInfo(exchange, crypto, fiat string, price, volume float64) { - if ExchangeInfoAlreadyExists(exchange, crypto, fiat, price, volume) { +// Add adds or updates the item stats +func Add(exchange string, p pair.CurrencyPair, assetType string, price, volume float64) { + if exchange == "" || assetType == "" || price == 0 || volume == 0 || p.FirstCurrency == "" || p.SecondCurrency == "" { return } - exch := ExchangeInfo{} - exch.Exchange = exchange - exch.FirstCurrency = crypto - exch.FiatCurrency = fiat - exch.Price = price - exch.Volume = volume - ExchInfo = append(ExchInfo, exch) + Append(exchange, p, assetType, price, volume) } -func ExchangeInfoAlreadyExists(exchange, crypto, fiat string, price, volume float64) bool { - for i := range ExchInfo { - if ExchInfo[i].Exchange == exchange && ExchInfo[i].FirstCurrency == crypto && ExchInfo[i].FiatCurrency == fiat { - ExchInfo[i].Price, ExchInfo[i].Volume = price, volume +// Append adds or updates the item stats for a specific +// currency pair and asset type +func Append(exchange string, p pair.CurrencyPair, assetType string, price, volume float64) { + if AlreadyExists(exchange, p, assetType, price, volume) { + return + } + + i := Item{ + Exchange: exchange, + Pair: p, + AssetType: assetType, + Price: price, + Volume: volume, + } + + Items = append(Items, i) +} + +// AlreadyExists checks to see if item info already exists +// for a specific currency pair and asset type +func AlreadyExists(exchange string, p pair.CurrencyPair, assetType string, price, volume float64) bool { + for i := range Items { + if Items[i].Exchange == exchange && Items[i].Pair.Equal(p) && Items[i].AssetType == assetType { + Items[i].Price, Items[i].Volume = price, volume return true } } return false } -func SortExchangesByVolume(crypto, fiat string, reverse bool) []ExchangeInfo { - info := []ExchangeInfo{} - - for _, x := range ExchInfo { - if x.FirstCurrency == crypto && x.FiatCurrency == fiat { - info = append(info, x) +// SortExchangesByVolume sorts item info by volume for a specific +// currency pair and asset type. Reverse will reverse the order from lowest to +// highest +func SortExchangesByVolume(p pair.CurrencyPair, assetType string, reverse bool) []Item { + var result []Item + for x := range Items { + if Items[x].Pair.Equal(p) && Items[x].AssetType == assetType { + result = append(result, Items[x]) } } if reverse { - sort.Sort(sort.Reverse(ByVolume(info))) + sort.Sort(sort.Reverse(ByVolume(result))) } else { - sort.Sort(ByVolume(info)) + sort.Sort(ByVolume(result)) } - return info + return result } -func SortExchangesByPrice(crypto, fiat string, reverse bool) []ExchangeInfo { - info := []ExchangeInfo{} - - for _, x := range ExchInfo { - if x.FirstCurrency == crypto && x.FiatCurrency == fiat { - info = append(info, x) +// SortExchangesByPrice sorts item info by volume for a specific +// currency pair and asset type. Reverse will reverse the order from lowest to +// highest +func SortExchangesByPrice(p pair.CurrencyPair, assetType string, reverse bool) []Item { + var result []Item + for x := range Items { + if Items[x].Pair.Equal(p) && Items[x].AssetType == assetType { + result = append(result, Items[x]) } } if reverse { - sort.Sort(sort.Reverse(ByPrice(info))) + sort.Sort(sort.Reverse(ByPrice(result))) } else { - sort.Sort(ByPrice(info)) + sort.Sort(ByPrice(result)) } - return info + return result } diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index 812823ef..3cefd2b9 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -2,138 +2,166 @@ package stats import ( "testing" + + "github.com/thrasher-/gocryptotrader/currency/pair" ) func TestLenByPrice(t *testing.T) { - exchangeInfo := ExchangeInfo{ - Exchange: "ANX", - FirstCurrency: "BTC", - FiatCurrency: "USD", - Price: 1200, - Volume: 5, + p := pair.NewCurrencyPair("BTC", "USD") + i := Item{ + Exchange: "ANX", + Pair: p, + AssetType: "SPOT", + Price: 1200, + Volume: 5, } - ExchInfo = append(ExchInfo, exchangeInfo) - if ByPrice.Len(ExchInfo) < 1 { + Items = append(Items, i) + if ByPrice.Len(Items) < 1 { t.Error("Test Failed - stats LenByPrice() length not correct.") } } func TestLessByPrice(t *testing.T) { - exchangeInfo := ExchangeInfo{ - Exchange: "alphapoint", - FirstCurrency: "BTC", - FiatCurrency: "USD", - Price: 1200, - Volume: 5, + p := pair.NewCurrencyPair("BTC", "USD") + i := Item{ + Exchange: "alphapoint", + Pair: p, + AssetType: "SPOT", + Price: 1200, + Volume: 5, } - exchangeInfo2 := ExchangeInfo{ - Exchange: "bitfinex", - FirstCurrency: "BTC", - FiatCurrency: "USD", - Price: 1198, - Volume: 20, + i2 := Item{ + Exchange: "bitfinex", + Pair: p, + AssetType: "SPOT", + Price: 1198, + Volume: 20, } - ExchInfo = append(ExchInfo, exchangeInfo) - ExchInfo = append(ExchInfo, exchangeInfo2) + Items = append(Items, i) + Items = append(Items, i2) - if !ByPrice.Less(ExchInfo, 2, 1) { + if !ByPrice.Less(Items, 2, 1) { t.Error("Test Failed - stats LessByPrice() incorrect return.") } - if ByPrice.Less(ExchInfo, 1, 2) { + if ByPrice.Less(Items, 1, 2) { t.Error("Test Failed - stats LessByPrice() incorrect return.") } } func TestSwapByPrice(t *testing.T) { - exchangeInfo := ExchangeInfo{ - Exchange: "bitstamp", - FirstCurrency: "BTC", - FiatCurrency: "USD", - Price: 1324, - Volume: 5, + p := pair.NewCurrencyPair("BTC", "USD") + i := Item{ + Exchange: "bitstamp", + Pair: p, + AssetType: "SPOT", + Price: 1324, + Volume: 5, } - exchangeInfo2 := ExchangeInfo{ - Exchange: "btcc", - FirstCurrency: "BTC", - FiatCurrency: "USD", - Price: 7863, - Volume: 20, + i2 := Item{ + Exchange: "btcc", + Pair: p, + AssetType: "SPOT", + Price: 7863, + Volume: 20, } - ExchInfo = append(ExchInfo, exchangeInfo) - ExchInfo = append(ExchInfo, exchangeInfo2) - ByPrice.Swap(ExchInfo, 3, 4) - if ExchInfo[3].Exchange != "btcc" || ExchInfo[4].Exchange != "bitstamp" { + Items = append(Items, i) + Items = append(Items, i2) + ByPrice.Swap(Items, 3, 4) + if Items[3].Exchange != "btcc" || Items[4].Exchange != "bitstamp" { t.Error("Test Failed - stats SwapByPrice did not swap values.") } } func TestLenByVolume(t *testing.T) { - if ByVolume.Len(ExchInfo) != 5 { + if ByVolume.Len(Items) != 5 { t.Error("Test Failed - stats lenByVolume did not swap values.") } } func TestLessByVolume(t *testing.T) { - if !ByVolume.Less(ExchInfo, 1, 2) { + if !ByVolume.Less(Items, 1, 2) { t.Error("Test Failed - stats LessByVolume() incorrect return.") } - if ByVolume.Less(ExchInfo, 2, 1) { + if ByVolume.Less(Items, 2, 1) { t.Error("Test Failed - stats LessByVolume() incorrect return.") } } func TestSwapByVolume(t *testing.T) { - ByPrice.Swap(ExchInfo, 3, 4) + ByPrice.Swap(Items, 3, 4) - if ExchInfo[4].Exchange != "btcc" || ExchInfo[3].Exchange != "bitstamp" { + if Items[4].Exchange != "btcc" || Items[3].Exchange != "bitstamp" { t.Error("Test Failed - stats SwapByVolume did not swap values.") } } -func TestAddExchangeInfo(t *testing.T) { - ExchInfo = ExchInfo[:0] - AddExchangeInfo("ANX", "BTC", "USD", 1200, 42) +func TestAdd(t *testing.T) { + Items = Items[:0] + p := pair.NewCurrencyPair("BTC", "USD") + Add("ANX", p, "SPOT", 1200, 42) - if len(ExchInfo) < 1 { - t.Error("Test Failed - stats AddExchangeInfo did not add exchange info.") + if len(Items) < 1 { + t.Error("Test Failed - stats Add did not add exchange info.") + } + + Add("", p, "", 0, 0) + + if len(Items) != 1 { + t.Error("Test Failed - stats Add did not add exchange info.") } } -func TestAppendExchangeInfo(t *testing.T) { - AppendExchangeInfo("sillyexchange", "BTC", "USD", 1234, 45) - if len(ExchInfo) < 2 { - t.Error("Test Failed - stats AppendExchangeInfo did not add exchange values.") +func TestAppend(t *testing.T) { + p := pair.NewCurrencyPair("BTC", "USD") + Append("sillyexchange", p, "SPOT", 1234, 45) + if len(Items) < 2 { + t.Error("Test Failed - stats Append did not add exchange values.") } - AppendExchangeInfo("sillyexchange", "BTC", "USD", 1234, 45) - if len(ExchInfo) == 3 { - t.Error("Test Failed - stats AppendExchangeInfo added exchange values") + + Append("sillyexchange", p, "SPOT", 1234, 45) + if len(Items) == 3 { + t.Error("Test Failed - stats Append added exchange values") } } -func TestExchangeInfoAlreadyExists(t *testing.T) { - if !ExchangeInfoAlreadyExists("ANX", "BTC", "USD", 1200, 42) { - t.Error("Test Failed - stats ExchangeInfoAlreadyExists exchange does not exist.") +func TestAlreadyExists(t *testing.T) { + p := pair.NewCurrencyPair("BTC", "USD") + if !AlreadyExists("ANX", p, "SPOT", 1200, 42) { + t.Error("Test Failed - stats AlreadyExists exchange does not exist.") } - if ExchangeInfoAlreadyExists("bla", "dii", "USD", 1234, 123) { - t.Error("Test Failed - stats ExchangeInfoAlreadyExists found incorrect exchange.") + p.FirstCurrency = "dii" + if AlreadyExists("bla", p, "SPOT", 1234, 123) { + t.Error("Test Failed - stats AlreadyExists found incorrect exchange.") } } func TestSortExchangesByVolume(t *testing.T) { - topVolume := SortExchangesByVolume("BTC", "USD", true) + p := pair.NewCurrencyPair("BTC", "USD") + topVolume := SortExchangesByVolume(p, "SPOT", true) if topVolume[0].Exchange != "sillyexchange" { t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") } + + topVolume = SortExchangesByVolume(p, "SPOT", false) + if topVolume[0].Exchange != "ANX" { + t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.") + } } func TestSortExchangesByPrice(t *testing.T) { - topPrice := SortExchangesByPrice("BTC", "USD", true) + p := pair.NewCurrencyPair("BTC", "USD") + topPrice := SortExchangesByPrice(p, "SPOT", true) if topPrice[0].Exchange != "sillyexchange" { t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") } + + topPrice = SortExchangesByPrice(p, "SPOT", false) + if topPrice[0].Exchange != "ANX" { + t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.") + } } diff --git a/routines.go b/routines.go index 3a20b24b..ae9f7c9d 100644 --- a/routines.go +++ b/routines.go @@ -5,10 +5,10 @@ import ( "log" "time" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) @@ -21,6 +21,7 @@ func printSummary(result ticker.Price, p pair.CurrencyPair, assetType, exchangeN return } + stats.Add(exchangeName, p, assetType, result.Last, result.Volume) log.Printf("%s %s %s: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", exchangeName, exchange.FormatCurrency(p).String(), From 2bd27feaf09d84073f3f65bb4f6c4c819169489f Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 4 Sep 2017 16:24:02 +1000 Subject: [PATCH 17/32] Polish websocket code --- config/config.go | 53 ++++++ config_routes.go | 74 -------- currency/currency.go | 6 +- currency/currency_test.go | 12 +- events/event_test.go | 183 ++++++++++++++------ events/events.go | 59 +++---- helpers.go | 108 ++++++++++++ main.go | 38 ++--- orderbook_routes.go | 141 ---------------- portfolio_routes.go | 27 --- restful_logger.go | 24 --- restful_router.go | 95 ++++++++++- restful_router_test.go | 11 -- restful_routes.go | 16 -- restful_server.go | 297 +++++++++++++++++++++++++++++++++ smsglobal/smsglobal.go | 7 +- ticker_routes.go | 139 --------------- ticker_routes_test.go | 1 - tools/websocket_client/main.go | 18 +- wallet_routes.go | 92 ---------- wallet_routes_test.go | 28 ---- websocket.go | 280 +++++++++++++++++++------------ 22 files changed, 916 insertions(+), 793 deletions(-) delete mode 100644 config_routes.go create mode 100644 helpers.go delete mode 100644 orderbook_routes.go delete mode 100644 portfolio_routes.go delete mode 100644 restful_logger.go delete mode 100644 restful_router_test.go delete mode 100644 restful_routes.go create mode 100644 restful_server.go delete mode 100644 ticker_routes.go delete mode 100644 ticker_routes_test.go delete mode 100644 wallet_routes.go delete mode 100644 wallet_routes_test.go diff --git a/config/config.go b/config/config.go index 322a13cd..2d01c15a 100644 --- a/config/config.go +++ b/config/config.go @@ -413,6 +413,22 @@ func (c *Config) LoadConfig(configPath string) error { return fmt.Errorf(ErrCheckingConfigValues, err) } + if c.SMS.Enabled { + err = c.CheckSMSGlobalConfigValues() + if err != nil { + log.Print(fmt.Errorf(ErrCheckingConfigValues, err)) + c.SMS.Enabled = false + } + } + + if c.Webserver.Enabled { + err = c.CheckWebserverConfigValues() + if err != nil { + log.Print(fmt.Errorf(ErrCheckingConfigValues, err)) + c.Webserver.Enabled = false + } + } + if c.CurrencyPairFormat == nil { c.CurrencyPairFormat = &CurrencyPairFormatConfig{ Delimiter: "-", @@ -423,6 +439,43 @@ func (c *Config) LoadConfig(configPath string) error { return nil } +// UpdateConfig updates the config with a supplied config file +func (c *Config) UpdateConfig(configPath string, newCfg Config) error { + if c.Name != newCfg.Name && newCfg.Name != "" { + c.Name = newCfg.Name + } + + err := newCfg.CheckExchangeConfigValues() + if err != nil { + return err + } + c.Exchanges = newCfg.Exchanges + + if c.CurrencyPairFormat != newCfg.CurrencyPairFormat { + c.CurrencyPairFormat = newCfg.CurrencyPairFormat + } + + c.Portfolio = newCfg.Portfolio + + err = newCfg.CheckSMSGlobalConfigValues() + if err != nil { + return err + } + c.SMS = newCfg.SMS + + err = c.SaveConfig(configPath) + if err != nil { + return err + } + + err = c.LoadConfig(configPath) + if err != nil { + return err + } + + return nil +} + // GetConfig returns a pointer to a confiuration object func GetConfig() *Config { return &Cfg diff --git a/config_routes.go b/config_routes.go deleted file mode 100644 index 8de3474e..00000000 --- a/config_routes.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "encoding/json" - "net/http" - - "github.com/thrasher-/gocryptotrader/config" -) - -// GetAllSettings replies to a request with an encoded JSON response about the -// trading bots configuration. -func GetAllSettings(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(bot.config); err != nil { - panic(err) - } -} - -// SaveAllSettings saves all current settings from request body as a JSON -// document then reloads state and returns the settings -func SaveAllSettings(w http.ResponseWriter, r *http.Request) { - //Get the data from the request - decoder := json.NewDecoder(r.Body) - var responseData config.Post - jsonerr := decoder.Decode(&responseData) - if jsonerr != nil { - panic(jsonerr) - } - //Save change the settings - for x := range bot.config.Exchanges { - for i := 0; i < len(responseData.Data.Exchanges); i++ { - if responseData.Data.Exchanges[i].Name == bot.config.Exchanges[x].Name { - bot.config.Exchanges[x].Enabled = responseData.Data.Exchanges[i].Enabled - bot.config.Exchanges[x].APIKey = responseData.Data.Exchanges[i].APIKey - bot.config.Exchanges[x].APISecret = responseData.Data.Exchanges[i].APISecret - bot.config.Exchanges[x].EnabledPairs = responseData.Data.Exchanges[i].EnabledPairs - } - } - } - //Reload the configuration - err := bot.config.SaveConfig(bot.configFile) - if err != nil { - panic(err) - } - err = bot.config.LoadConfig(bot.configFile) - if err != nil { - panic(err) - } - setupBotExchanges() - //Return response status - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(bot.config); err != nil { - panic(err) - } -} - -// ConfigRoutes declares the current routes for config_routes.go -var ConfigRoutes = Routes{ - Route{ - "GetAllSettings", - "GET", - "/config/all", - GetAllSettings, - }, - - Route{ - "SaveAllSettings", - "POST", - "/config/all/save", - SaveAllSettings, - }, -} diff --git a/currency/currency.go b/currency/currency.go index e29d8d7c..fe4a2b5b 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -68,7 +68,7 @@ var ( ErrCurrencyNotFound = errors.New("unable to find specified currency") ErrQueryingYahoo = errors.New("unable to query Yahoo currency values") ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data") - yahooEnabled = false + YahooEnabled = false ) // IsDefaultCurrency checks if the currency passed in matches the default @@ -182,7 +182,7 @@ func SeedCurrencyData(fiatCurrencies string) error { fiatCurrencies = DefaultCurrencies } - if yahooEnabled { + if YahooEnabled { return QueryYahooCurrencyValues(fiatCurrencies) } @@ -215,7 +215,7 @@ func ConvertCurrency(amount float64, from, to string) (float64, error) { return amount, nil } - if yahooEnabled { + if YahooEnabled { currency := from + to _, ok := CurrencyStore[currency] if !ok { diff --git a/currency/currency_test.go b/currency/currency_test.go index e7a0ad00..3f354e01 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -264,7 +264,7 @@ func TestCheckAndAddCurrency(t *testing.T) { } func TestSeedCurrencyData(t *testing.T) { - if yahooEnabled { + if YahooEnabled { currencyRequestDefault := "" currencyRequestUSDAUD := "USD,AUD" currencyRequestObtuse := "WigWham" @@ -292,7 +292,7 @@ func TestSeedCurrencyData(t *testing.T) { } } - yahooEnabled = false + YahooEnabled = false err := SeedCurrencyData("") if err != nil { t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err) @@ -313,7 +313,7 @@ func TestMakecurrencyPairs(t *testing.T) { } func TestConvertCurrency(t *testing.T) { - if yahooEnabled { + if YahooEnabled { fiatCurrencies := DefaultCurrencies for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { @@ -336,7 +336,7 @@ func TestConvertCurrency(t *testing.T) { } } - yahooEnabled = false + YahooEnabled = false _, err := ConvertCurrency(1000, "USD", "AUD") if err != nil { t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) @@ -383,7 +383,7 @@ func TestFetchFixerCurrencyData(t *testing.T) { } func TestFetchYahooCurrencyData(t *testing.T) { - if !yahooEnabled { + if !YahooEnabled { return } @@ -407,7 +407,7 @@ func TestFetchYahooCurrencyData(t *testing.T) { } func TestQueryYahooCurrencyValues(t *testing.T) { - if !yahooEnabled { + if !YahooEnabled { return } diff --git a/events/event_test.go b/events/event_test.go index c33c37bd..a57ef5d0 100644 --- a/events/event_test.go +++ b/events/event_test.go @@ -2,40 +2,42 @@ package events import ( "testing" + + "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) func TestAddEvent(t *testing.T) { - eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + pair := pair.NewCurrencyPair("BTC", "USD") + eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil && eventID != 0 { t.Errorf("Test Failed. AddEvent: Error, %s", err) } - eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + eventID, err = AddEvent("ANXX", "price", ">,==", pair, "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Exchange") } - eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", "SPOT", actionTest) + eventID, err = AddEvent("ANX", "prices", ">,==", pair, "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Item") } - eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", "SPOT", actionTest) + eventID, err = AddEvent("ANX", "price", "3===D", pair, "SPOT", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Condition") } - eventID, err = AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", "console_prints") - if err == nil && eventID == 0 { - t.Error("Test Failed. AddEvent: Error, error not captured in Action") - } - eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", "SPOT", actionTest) + eventID, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", "console_prints") if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Action") } + if !RemoveEvent(eventID) { t.Error("Test Failed. RemoveEvent: Error, error removing event") } } func TestRemoveEvent(t *testing.T) { - eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + pair := pair.NewCurrencyPair("BTC", "USD") + eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil && eventID != 0 { t.Errorf("Test Failed. RemoveEvent: Error, %s", err) } @@ -48,15 +50,16 @@ func TestRemoveEvent(t *testing.T) { } func TestGetEventCounter(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + pair := pair.NewCurrencyPair("BTC", "USD") + one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } - two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + two, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } - three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + three, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } @@ -84,9 +87,10 @@ func TestGetEventCounter(t *testing.T) { } func TestExecuteAction(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + pair := pair.NewCurrencyPair("BTC", "USD") + one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { - t.Errorf("Test Failed. ExecuteAction: Error, %s", err) + t.Fatalf("Test Failed. ExecuteAction: Error, %s", err) } isExecuted := Events[one].ExecuteAction() if !isExecuted { @@ -96,17 +100,46 @@ func TestExecuteAction(t *testing.T) { t.Error("Test Failed. ExecuteAction: Error, error removing event") } + action := actionSMSNotify + "," + "ALL" + one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action) + if err != nil { + t.Fatalf("Test Failed. ExecuteAction: Error, %s", err) + } + + isExecuted = Events[one].ExecuteAction() + if !isExecuted { + t.Error("Test Failed. ExecuteAction: Error, error removing event") + } + if !RemoveEvent(one) { + t.Error("Test Failed. ExecuteAction: Error, error removing event") + } + + action = actionSMSNotify + "," + "StyleGherkin" + one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action) + if err != nil { + t.Fatalf("Test Failed. ExecuteAction: Error, %s", err) + } + + isExecuted = Events[one].ExecuteAction() + if !isExecuted { + t.Error("Test Failed. ExecuteAction: Error, error removing event") + } + if !RemoveEvent(one) { + t.Error("Test Failed. ExecuteAction: Error, error removing event") + } + // More tests when ExecuteAction is expanded } func TestEventToString(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) + pair := pair.NewCurrencyPair("BTC", "USD") + one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { t.Errorf("Test Failed. EventToString: Error, %s", err) } - eventString := Events[one].EventToString() - if eventString != "If the BTCLTC [SPOT] price on ANX is > == then ACTION_TEST." { + eventString := Events[one].String() + if eventString != "If the BTCUSD [SPOT] price on ANX is > == then ACTION_TEST." { t.Error("Test Failed. EventToString: Error, incorrect return string") } @@ -115,64 +148,118 @@ func TestEventToString(t *testing.T) { } } -func TestCheckCondition(t *testing.T) { //error handling needs to be implemented - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest) +func TestCheckCondition(t *testing.T) { + // Test invalid currency pair + newPair := pair.NewCurrencyPair("A", "B") + one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest) if err != nil { - t.Errorf("Test Failed. EventToString: Error, %s", err) + t.Errorf("Test Failed. CheckCondition: Error, %s", err) + } + conditionBool := Events[one].CheckCondition() + if conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") } - conditionBool := Events[one].CheckCondition() - if conditionBool { //check once error handling is implemented - t.Error("Test Failed. EventToString: Error, wrong conditional.") + // Test last price == 0 + var tickerNew ticker.Price + tickerNew.Last = 0 + newPair = pair.NewCurrencyPair("BTC", "USD") + ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot) + Events[one].Pair = newPair + conditionBool = Events[one].CheckCondition() + if conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") + } + + // Test last pricce > 0 and conditional logic + tickerNew.Last = 11 + ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot) + Events[one].Condition = ">,10" + conditionBool = Events[one].CheckCondition() + if !conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") + } + + // Test last price >= 10 + Events[one].Condition = ">=,10" + conditionBool = Events[one].CheckCondition() + if !conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") + } + + // Test last price <= 10 + Events[one].Condition = "<,100" + conditionBool = Events[one].CheckCondition() + if !conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") + } + + // Test last price <= 10 + Events[one].Condition = "<=,100" + conditionBool = Events[one].CheckCondition() + if !conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") + } + + Events[one].Condition = "==,11" + conditionBool = Events[one].CheckCondition() + if !conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") + } + + Events[one].Condition = "^,11" + conditionBool = Events[one].CheckCondition() + if conditionBool { + t.Error("Test Failed. CheckCondition: Error, wrong conditional.") } if !RemoveEvent(one) { - t.Error("Test Failed. EventToString: Error, error removing event") + t.Error("Test Failed. CheckCondition: Error, error removing event") } } func TestIsValidEvent(t *testing.T) { err := IsValidEvent("ANX", "price", ">,==", actionTest) if err != nil { - t.Errorf("Test Failed. IsValidExchange: Error %s", err) + t.Errorf("Test Failed. IsValidEvent: %s", err) } err = IsValidEvent("ANX", "price", ">,", actionTest) if err == nil { - t.Errorf("Test Failed. IsValidExchange: Error") + t.Errorf("Test Failed. IsValidEvent: %s", err) } err = IsValidEvent("ANX", "Testy", ">,==", actionTest) if err == nil { - t.Errorf("Test Failed. IsValidExchange: Error") + t.Errorf("Test Failed. IsValidEvent: %s", err) } err = IsValidEvent("Testys", "price", ">,==", actionTest) if err == nil { - t.Errorf("Test Failed. IsValidExchange: Error") + t.Errorf("Test Failed. IsValidEvent: %s", err) + } + + action := "blah,blah" + err = IsValidEvent("ANX", "price", ">=,10", action) + if err == nil { + t.Errorf("Test Failed. IsValidEvent: %s", err) + } + + action = "SMS,blah" + err = IsValidEvent("ANX", "price", ">=,10", action) + if err == nil { + t.Errorf("Test Failed. IsValidEvent: %s", err) } //Function tests need to appended to this function when more actions are //implemented } -func TestCheckEvents(t *testing.T) { //Add error handling - //CheckEvents() //check once error handling is implemented -} +func TestCheckEvents(t *testing.T) { + pair := pair.NewCurrencyPair("BTC", "USD") + _, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest) + if err != nil { + t.Fatal("Test failed. TestChcheckEvents add event") + } -func TestIsValidCurrency(t *testing.T) { - if !IsValidCurrency("BTC") { - t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") - } - if !IsValidCurrency("USD") { - t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") - } - if IsValidCurrency("testy") { - t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") - } - if !IsValidCurrency("USD", "BTC", "USD") { - t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") - } - if IsValidCurrency("USD", "USD", "Wigwham") { - t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") - } + go CheckEvents() } func TestIsValidExchange(t *testing.T) { diff --git a/events/events.go b/events/events.go index 9105ec06..e14a11c3 100644 --- a/events/events.go +++ b/events/events.go @@ -8,7 +8,6 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges/ticker" "github.com/thrasher-/gocryptotrader/smsglobal" @@ -32,20 +31,18 @@ var ( errInvalidCondition = errors.New("invalid conditional option") errInvalidAction = errors.New("invalid action") errExchangeDisabled = errors.New("desired exchange is disabled") - errCurrencyInvalid = errors.New("invalid currency") ) // Event struct holds the event variables type Event struct { - ID int - Exchange string - Item string - Condition string - FirstCurrency string - Asset string - SecondCurrency string - Action string - Executed bool + ID int + Exchange string + Item string + Condition string + Pair pair.CurrencyPair + Asset string + Action string + Executed bool } // Events variable is a pointer array to the event structures that will be @@ -54,16 +51,12 @@ var Events []*Event // AddEvent adds an event to the Events chain and returns an index/eventID // and an error -func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Asset, Action string) (int, error) { +func AddEvent(Exchange, Item, Condition string, CurrencyPair pair.CurrencyPair, Asset, Action string) (int, error) { err := IsValidEvent(Exchange, Item, Condition, Action) if err != nil { return 0, err } - if !IsValidCurrency(FirstCurrency, SecondCurrency) { - return 0, errCurrencyInvalid - } - Event := &Event{} if len(Events) == 0 { @@ -75,8 +68,7 @@ func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Asset, A Event.Exchange = Exchange Event.Item = Item Event.Condition = Condition - Event.FirstCurrency = FirstCurrency - Event.SecondCurrency = SecondCurrency + Event.Pair = CurrencyPair Event.Asset = Asset Event.Action = Action Event.Executed = false @@ -114,7 +106,7 @@ func (e *Event) ExecuteAction() bool { if common.StringContains(e.Action, ",") { action := common.SplitStrings(e.Action, ",") if action[0] == actionSMSNotify { - message := fmt.Sprintf("Event triggered: %s", e.EventToString()) + message := fmt.Sprintf("Event triggered: %s", e.String()) if action[1] == "ALL" { smsglobal.SMSSendToAll(message, config.Cfg) } else { @@ -124,17 +116,17 @@ func (e *Event) ExecuteAction() bool { } } } else { - log.Printf("Event triggered: %s", e.EventToString()) + log.Printf("Event triggered: %s", e.String()) } return true } // EventToString turns the structure event into a string -func (e *Event) EventToString() string { +func (e *Event) String() string { condition := common.SplitStrings(e.Condition, ",") return fmt.Sprintf( - "If the %s%s [%s] %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency, - e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action, + "If the %s%s [%s] %s on %s is %s then %s.", e.Pair.FirstCurrency.String(), + e.Pair.SecondCurrency.String(), e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action, ) } @@ -144,12 +136,12 @@ func (e *Event) CheckCondition() bool { condition := common.SplitStrings(e.Condition, ",") targetPrice, _ := strconv.ParseFloat(condition[1], 64) - ticker, err := ticker.GetTickerByExchange(e.Exchange) + t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset) if err != nil { return false } - lastPrice := ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)][e.Asset].Last + lastPrice := t.Last if lastPrice == 0 { return false @@ -226,8 +218,9 @@ func IsValidEvent(Exchange, Item, Condition, Action string) error { return errInvalidAction } + cfg := config.GetConfig() if action[1] != "ALL" && smsglobal.SMSGetNumberByName( - action[1], config.Cfg.SMS) == smsglobal.ErrSMSContactNotFound { + action[1], cfg.SMS) == smsglobal.ErrSMSContactNotFound { return errInvalidAction } } else { @@ -260,20 +253,6 @@ func CheckEvents() { } } -// IsValidCurrency takes in CRYPTO or FIAT currency strings and returns if valid -func IsValidCurrency(currencies ...string) bool { - for index, whatIsIt := range currencies { - whatIsIt = common.StringToUpper(whatIsIt) - if currency.IsDefaultCryptocurrency(whatIsIt) || currency.IsDefaultCurrency(whatIsIt) { - if len(currencies)-1 == index { - return true - } - continue - } - } - return false -} - // IsValidExchange validates the exchange func IsValidExchange(Exchange, configPath string) bool { Exchange = common.StringToUpper(Exchange) diff --git a/helpers.go b/helpers.go new file mode 100644 index 00000000..554f66ff --- /dev/null +++ b/helpers.go @@ -0,0 +1,108 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/thrasher-/gocryptotrader/exchanges/stats" + + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// GetSpecificOrderbook returns a specific orderbook given the currency, +// exchangeName and assetType +func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) { + var specificOrderbook orderbook.Base + var err error + for i := 0; i < len(bot.exchanges); i++ { + if bot.exchanges[i] != nil { + if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { + specificOrderbook, err = bot.exchanges[i].GetOrderbookEx( + pair.NewCurrencyPairFromString(currency), + assetType, + ) + break + } + } + } + return specificOrderbook, err +} + +// GetSpecificTicker returns a specific ticker given the currency, +// exchangeName and assetType +func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) { + var specificTicker ticker.Price + var err error + for i := 0; i < len(bot.exchanges); i++ { + if bot.exchanges[i] != nil { + if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { + specificTicker, err = bot.exchanges[i].GetTickerPrice( + pair.NewCurrencyPairFromString(currency), + assetType, + ) + break + } + } + } + return specificTicker, err +} + +// GetCollatedExchangeAccountInfoByCoin collates individual exchange account +// information and turns into into a map string of +// exchange.AccountCurrencyInfo +func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo { + result := make(map[string]exchange.AccountCurrencyInfo) + for i := 0; i < len(accounts); i++ { + for j := 0; j < len(accounts[i].Currencies); j++ { + currencyName := accounts[i].Currencies[j].CurrencyName + avail := accounts[i].Currencies[j].TotalValue + onHold := accounts[i].Currencies[j].Hold + + info, ok := result[currencyName] + if !ok { + accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail} + result[currencyName] = accountInfo + } else { + info.Hold += onHold + info.TotalValue += avail + result[currencyName] = info + } + } + } + return result +} + +// GetAccountCurrencyInfoByExchangeName returns info for an exchange +func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) { + for i := 0; i < len(accounts); i++ { + if accounts[i].ExchangeName == exchangeName { + return accounts[i], nil + } + } + return exchange.AccountInfo{}, errors.New(exchange.ErrExchangeNotFound) +} + +// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest +// price for a given currency pair and asset type +func GetExchangeHighestPriceByCurrencyPair(p pair.CurrencyPair, assetType string) (string, error) { + result := stats.SortExchangesByPrice(p, assetType, true) + if len(result) != 1 { + return "", fmt.Errorf("no stats for supplied currency pair and asset type") + } + + return result[0].Exchange, nil +} + +// GetExchangeLowestPriceByCurrencyPair returns the exchange with the lowest +// price for a given currency pair and asset type +func GetExchangeLowestPriceByCurrencyPair(p pair.CurrencyPair, assetType string) (string, error) { + result := stats.SortExchangesByPrice(p, assetType, false) + if len(result) != 1 { + return "", fmt.Errorf("no stats for supplied currency pair and asset type") + } + + return result[0].Exchange, nil +} diff --git a/main.go b/main.go index 08a6b9d8..818b2081 100644 --- a/main.go +++ b/main.go @@ -119,16 +119,10 @@ func main() { AdjustGoMaxProcs() if bot.config.SMS.Enabled { - err = bot.config.CheckSMSGlobalConfigValues() - if err != nil { - log.Println(err) // non fatal event - bot.config.SMS.Enabled = false - } else { - log.Printf( - "SMS support enabled. Number of SMS contacts %d.\n", - smsglobal.GetEnabledSMSContacts(bot.config.SMS), - ) - } + log.Printf( + "SMS support enabled. Number of SMS contacts %d.\n", + smsglobal.GetEnabledSMSContacts(bot.config.SMS), + ) } else { log.Println("SMS support disabled.") } @@ -174,7 +168,6 @@ func main() { setupBotExchanges() bot.config.RetrieveConfigCurrencyPairs() - err = currency.SeedCurrencyData(currency.BaseCurrencies) if err != nil { log.Fatalf("Fatal error retrieving config currencies. Error: %s", err) @@ -194,21 +187,14 @@ func main() { go OrderbookUpdaterRoutine() if bot.config.Webserver.Enabled { - err := bot.config.CheckWebserverConfigValues() - if err != nil { - log.Println(err) // non fatal event - //bot.config.Webserver.Enabled = false - } else { - listenAddr := bot.config.Webserver.ListenAddress - log.Printf( - "HTTP RESTful Webserver support enabled. Listen URL: http://%s:%d/\n", - common.ExtractHost(listenAddr), common.ExtractPort(listenAddr), - ) - router := NewRouter(bot.exchanges) - log.Fatal(http.ListenAndServe(listenAddr, router)) - } - } - if !bot.config.Webserver.Enabled { + listenAddr := bot.config.Webserver.ListenAddress + log.Printf( + "HTTP Webserver support enabled. Listen URL: http://%s:%d/\n", + common.ExtractHost(listenAddr), common.ExtractPort(listenAddr), + ) + router := NewRouter(bot.exchanges) + log.Fatal(http.ListenAndServe(listenAddr, router)) + } else { log.Println("HTTP RESTful Webserver support disabled.") } diff --git a/orderbook_routes.go b/orderbook_routes.go deleted file mode 100644 index 6a708739..00000000 --- a/orderbook_routes.go +++ /dev/null @@ -1,141 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "net/http" - - "github.com/gorilla/mux" - "github.com/thrasher-/gocryptotrader/currency/pair" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" -) - -// GetSpecificOrderbook returns a specific orderbook given the currency, -// exchangeName and assetType -func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) { - var specificOrderbook orderbook.Base - var err error - for i := 0; i < len(bot.exchanges); i++ { - if bot.exchanges[i] != nil { - if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { - specificOrderbook, err = bot.exchanges[i].GetOrderbookEx( - pair.NewCurrencyPairFromString(currency), - assetType, - ) - break - } - } - } - return specificOrderbook, err -} - -func jsonOrderbookResponse(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - currency := vars["currency"] - exchange := vars["exchangeName"] - assetType := vars["assetType"] - - if assetType == "" { - assetType = orderbook.Spot - } - - response, err := GetSpecificOrderbook(currency, exchange, assetType) - if err != nil { - log.Printf("Failed to fetch orderbook for %s currency: %s\n", exchange, - currency) - return - } - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } -} - -// AllEnabledExchangeOrderbooks holds the enabled exchange orderbooks -type AllEnabledExchangeOrderbooks struct { - Data []EnabledExchangeOrderbooks `json:"data"` -} - -// EnabledExchangeOrderbooks is a sub type for singular exchanges and respective -// orderbooks -type EnabledExchangeOrderbooks struct { - ExchangeName string `json:"exchangeName"` - ExchangeValues []orderbook.Base `json:"exchangeValues"` -} - -// GetAllActiveOrderbooks returns all enabled exchanges orderbooks -func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks { - var orderbookData []EnabledExchangeOrderbooks - - for _, individualBot := range bot.exchanges { - if individualBot != nil && individualBot.IsEnabled() { - var individualExchange EnabledExchangeOrderbooks - exchangeName := individualBot.GetName() - individualExchange.ExchangeName = exchangeName - currencies := individualBot.GetEnabledCurrencies() - assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Printf("failed to get %s exchange asset types. Error: %s", - exchangeName, err) - continue - } - for _, x := range currencies { - currency := x - - var ob orderbook.Base - if len(assetTypes) > 1 { - for y := range assetTypes { - ob, err = individualBot.UpdateOrderbook(currency, - assetTypes[y]) - } - } else { - ob, err = individualBot.UpdateOrderbook(currency, - assetTypes[0]) - } - - if err != nil { - log.Printf("failed to get %s %s orderbook. Error: %s", - currency.Pair().String(), - exchangeName, - err) - continue - } - - individualExchange.ExchangeValues = append( - individualExchange.ExchangeValues, ob, - ) - } - orderbookData = append(orderbookData, individualExchange) - } - } - return orderbookData -} - -func getAllActiveOrderbooksResponse(w http.ResponseWriter, r *http.Request) { - var response AllEnabledExchangeOrderbooks - response.Data = GetAllActiveOrderbooks() - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } -} - -// OrderbookRoutes denotes the current exchange orderbook routes -var OrderbookRoutes = Routes{ - Route{ - "AllActiveExchangesAndOrderbooks", - "GET", - "/exchanges/orderbook/latest/all", - getAllActiveOrderbooksResponse, - }, - Route{ - "IndividualExchangeOrderbook", - "GET", - "/exchanges/{exchangeName}/orderbook/latest/{currency}", - jsonOrderbookResponse, - }, -} diff --git a/portfolio_routes.go b/portfolio_routes.go deleted file mode 100644 index d2117160..00000000 --- a/portfolio_routes.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "encoding/json" - "net/http" -) - -// RESTGetPortfolio replies to a request with an encoded JSON response of the -// portfolio -func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) { - result := bot.portfolio.GetPortfolioSummary() - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(result); err != nil { - panic(err) - } -} - -// PortfolioRoutes declares the current routes for config_routes.go -var PortfolioRoutes = Routes{ - Route{ - "GetPortfolio", - "GET", - "/portfolio/all", - RESTGetPortfolio, - }, -} diff --git a/restful_logger.go b/restful_logger.go deleted file mode 100644 index 2025c5a3..00000000 --- a/restful_logger.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "log" - "net/http" - "time" -) - -// Logger logs the requests internally -func Logger(inner http.Handler, name string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - - inner.ServeHTTP(w, r) - - log.Printf( - "%s\t%s\t%s\t%s", - r.Method, - r.RequestURI, - name, - time.Since(start), - ) - }) -} diff --git a/restful_router.go b/restful_router.go index f151bb3d..bd335797 100644 --- a/restful_router.go +++ b/restful_router.go @@ -2,27 +2,104 @@ package main import ( "fmt" + "log" "net/http" + "time" "github.com/gorilla/mux" "github.com/thrasher-/gocryptotrader/exchanges" ) +// RESTLogger logs the requests internally +func RESTLogger(inner http.Handler, name string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + inner.ServeHTTP(w, r) + + log.Printf( + "%s\t%s\t%s\t%s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) +} + +// Route is a sub type that holds the request routes +type Route struct { + Name string + Method string + Pattern string + HandlerFunc http.HandlerFunc +} + +// Routes is an array of all the registered routes +type Routes []Route + +var routes = Routes{} + // NewRouter takes in the exchange interfaces and returns a new multiplexor // router func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { router := mux.NewRouter().StrictSlash(true) - allRoutes := append(routes, ExchangeRoutes...) - allRoutes = append(allRoutes, ConfigRoutes...) - allRoutes = append(allRoutes, PortfolioRoutes...) - allRoutes = append(allRoutes, WalletRoutes...) - allRoutes = append(allRoutes, IndexRoute...) - allRoutes = append(allRoutes, WebsocketRoutes...) - allRoutes = append(allRoutes, OrderbookRoutes...) - for _, route := range allRoutes { + + routes = Routes{ + Route{ + "GetAllSettings", + "GET", + "/config/all", + RESTGetAllSettings, + }, + Route{ + "SaveAllSettings", + "POST", + "/config/all/save", + RESTSaveAllSettings, + }, + Route{ + "AllEnabledAccountInfo", + "GET", + "/exchanges/enabled/accounts/all", + RESTGetAllEnabledAccountInfo, + }, + Route{ + "AllActiveExchangesAndCurrencies", + "GET", + "/exchanges/enabled/latest/all", + RESTGetAllActiveTickers, + }, + Route{ + "IndividualExchangeAndCurrency", + "GET", + "/exchanges/{exchangeName}/latest/{currency}", + RESTGetTicker, + }, + Route{ + "GetPortfolio", + "GET", + "/portfolio/all", + RESTGetPortfolio, + }, + Route{ + "AllActiveExchangesAndOrderbooks", + "GET", + "/exchanges/orderbook/latest/all", + RESTGetAllActiveOrderbooks, + }, + Route{ + "IndividualExchangeOrderbook", + "GET", + "/exchanges/{exchangeName}/orderbook/latest/{currency}", + RESTGetOrderbook, + }, + } + + for _, route := range routes { var handler http.Handler handler = route.HandlerFunc - handler = Logger(handler, route.Name) + handler = RESTLogger(handler, route.Name) router. Methods(route.Method). diff --git a/restful_router_test.go b/restful_router_test.go deleted file mode 100644 index e7fd9cf8..00000000 --- a/restful_router_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "testing" -) - -func TestNewRouter(t *testing.T) { - if value := NewRouter(bot.exchanges); value.KeepContext { - t.Error("Test Failed - Restful_Router_Test.go - NewRouter Error") - } -} diff --git a/restful_routes.go b/restful_routes.go deleted file mode 100644 index 4728f9be..00000000 --- a/restful_routes.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import "net/http" - -// Route is a sub type that holds the request routes -type Route struct { - Name string - Method string - Pattern string - HandlerFunc http.HandlerFunc -} - -// Routes is an array of all the registered routes -type Routes []Route - -var routes = Routes{} diff --git a/restful_server.go b/restful_server.go new file mode 100644 index 00000000..f5103227 --- /dev/null +++ b/restful_server.go @@ -0,0 +1,297 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/thrasher-/gocryptotrader/config" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// AllEnabledExchangeOrderbooks holds the enabled exchange orderbooks +type AllEnabledExchangeOrderbooks struct { + Data []EnabledExchangeOrderbooks `json:"data"` +} + +// EnabledExchangeOrderbooks is a sub type for singular exchanges and respective +// orderbooks +type EnabledExchangeOrderbooks struct { + ExchangeName string `json:"exchangeName"` + ExchangeValues []orderbook.Base `json:"exchangeValues"` +} + +// AllEnabledExchangeCurrencies holds the enabled exchange currencies +type AllEnabledExchangeCurrencies struct { + Data []EnabledExchangeCurrencies `json:"data"` +} + +// EnabledExchangeCurrencies is a sub type for singular exchanges and respective +// currencies +type EnabledExchangeCurrencies struct { + ExchangeName string `json:"exchangeName"` + ExchangeValues []ticker.Price `json:"exchangeValues"` +} + +// AllEnabledExchangeAccounts holds all enabled accounts info +type AllEnabledExchangeAccounts struct { + Data []exchange.AccountInfo `json:"data"` +} + +// RESTfulJSONResponse outputs a JSON response of the req interface +func RESTfulJSONResponse(w http.ResponseWriter, r *http.Request, req interface{}) error { + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(req); err != nil { + return err + } + return nil +} + +// RESTfulError prints the REST method and error +func RESTfulError(method string, err error) { + log.Printf("RESTful %s: server failed to send JSON response. Error %s", + method, err) +} + +// RESTGetAllSettings replies to a request with an encoded JSON response about the +// trading bots configuration. +func RESTGetAllSettings(w http.ResponseWriter, r *http.Request) { + err := RESTfulJSONResponse(w, r, bot.config) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// RESTSaveAllSettings saves all current settings from request body as a JSON +// document then reloads state and returns the settings +func RESTSaveAllSettings(w http.ResponseWriter, r *http.Request) { + //Get the data from the request + decoder := json.NewDecoder(r.Body) + var responseData config.Post + err := decoder.Decode(&responseData) + if err != nil { + RESTfulError(r.Method, err) + } + //Save change the settings + err = bot.config.UpdateConfig(bot.configFile, responseData.Data) + if err != nil { + RESTfulError(r.Method, err) + } + + err = RESTfulJSONResponse(w, r, bot.config) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// RESTGetOrderbook returns orderbook info for a given currency, exchange and +// asset type +func RESTGetOrderbook(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + currency := vars["currency"] + exchange := vars["exchangeName"] + assetType := vars["assetType"] + + if assetType == "" { + assetType = orderbook.Spot + } + + response, err := GetSpecificOrderbook(currency, exchange, assetType) + if err != nil { + log.Printf("Failed to fetch orderbook for %s currency: %s\n", exchange, + currency) + return + } + + err = RESTfulJSONResponse(w, r, response) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// GetAllActiveOrderbooks returns all enabled exchanges orderbooks +func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks { + var orderbookData []EnabledExchangeOrderbooks + + for _, individualBot := range bot.exchanges { + if individualBot != nil && individualBot.IsEnabled() { + var individualExchange EnabledExchangeOrderbooks + exchangeName := individualBot.GetName() + individualExchange.ExchangeName = exchangeName + currencies := individualBot.GetEnabledCurrencies() + assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) + if err != nil { + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) + continue + } + for _, x := range currencies { + currency := x + + var ob orderbook.Base + if len(assetTypes) > 1 { + for y := range assetTypes { + ob, err = individualBot.GetOrderbookEx(currency, + assetTypes[y]) + } + } else { + ob, err = individualBot.GetOrderbookEx(currency, + assetTypes[0]) + } + + if err != nil { + log.Printf("failed to get %s %s orderbook. Error: %s", + currency.Pair().String(), + exchangeName, + err) + continue + } + + individualExchange.ExchangeValues = append( + individualExchange.ExchangeValues, ob, + ) + } + orderbookData = append(orderbookData, individualExchange) + } + } + return orderbookData +} + +// RESTGetAllActiveOrderbooks returns all enabled exchange orderbooks +func RESTGetAllActiveOrderbooks(w http.ResponseWriter, r *http.Request) { + var response AllEnabledExchangeOrderbooks + response.Data = GetAllActiveOrderbooks() + + err := RESTfulJSONResponse(w, r, response) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// RESTGetPortfolio returns the bot portfolio +func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) { + result := bot.portfolio.GetPortfolioSummary() + err := RESTfulJSONResponse(w, r, result) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// RESTGetTicker returns ticker info for a given currency, exchange and +// asset type +func RESTGetTicker(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + currency := vars["currency"] + exchange := vars["exchangeName"] + assetType := vars["assetType"] + + if assetType == "" { + assetType = ticker.Spot + } + response, err := GetSpecificTicker(currency, exchange, assetType) + if err != nil { + log.Printf("Failed to fetch ticker for %s currency: %s\n", exchange, + currency) + return + } + err = RESTfulJSONResponse(w, r, response) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// GetAllActiveTickers returns all enabled exchange tickers +func GetAllActiveTickers() []EnabledExchangeCurrencies { + var tickerData []EnabledExchangeCurrencies + + for _, individualBot := range bot.exchanges { + if individualBot != nil && individualBot.IsEnabled() { + var individualExchange EnabledExchangeCurrencies + exchangeName := individualBot.GetName() + individualExchange.ExchangeName = exchangeName + log.Println( + "Getting enabled currencies for '" + exchangeName + "'", + ) + currencies := individualBot.GetEnabledCurrencies() + for _, x := range currencies { + currency := x + assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) + if err != nil { + log.Printf("failed to get %s exchange asset types. Error: %s", + exchangeName, err) + continue + } + var tickerPrice ticker.Price + if len(assetTypes) > 1 { + for y := range assetTypes { + tickerPrice, err = individualBot.GetTickerPrice(currency, + assetTypes[y]) + } + } else { + tickerPrice, err = individualBot.GetTickerPrice(currency, + assetTypes[0]) + } + + if err != nil { + log.Printf("failed to get %s %s ticker. Error: %s", + currency.Pair().String(), + exchangeName, + err) + continue + } + + individualExchange.ExchangeValues = append( + individualExchange.ExchangeValues, tickerPrice, + ) + } + tickerData = append(tickerData, individualExchange) + } + } + return tickerData +} + +// RESTGetAllActiveTickers returns all active tickers +func RESTGetAllActiveTickers(w http.ResponseWriter, r *http.Request) { + var response AllEnabledExchangeCurrencies + response.Data = GetAllActiveTickers() + + err := RESTfulJSONResponse(w, r, response) + if err != nil { + RESTfulError(r.Method, err) + } +} + +// GetAllEnabledExchangeAccountInfo returns all the current enabled exchanges +func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts { + var response AllEnabledExchangeAccounts + for _, individualBot := range bot.exchanges { + if individualBot != nil && individualBot.IsEnabled() { + if !individualBot.GetAuthenticatedAPISupport() { + log.Printf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName()) + continue + } + individualExchange, err := individualBot.GetExchangeAccountInfo() + if err != nil { + log.Printf("Error encountered retrieving exchange account info for %s. Error %s", + individualBot.GetName(), err) + continue + } + response.Data = append(response.Data, individualExchange) + } + } + return response +} + +// RESTGetAllEnabledAccountInfo via get request returns JSON response of account +// info +func RESTGetAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) { + response := GetAllEnabledExchangeAccountInfo() + err := RESTfulJSONResponse(w, r, response) + if err != nil { + RESTfulError(r.Method, err) + } +} diff --git a/smsglobal/smsglobal.go b/smsglobal/smsglobal.go index 803407ab..55fa03ca 100644 --- a/smsglobal/smsglobal.go +++ b/smsglobal/smsglobal.go @@ -2,6 +2,7 @@ package smsglobal import ( "errors" + "flag" "log" "net/url" "strings" @@ -44,7 +45,7 @@ func SMSSendToAll(message string, cfg config.Config) { // SMSGetNumberByName returns contact number by supplied name func SMSGetNumberByName(name string, smsCfg config.SMSGlobalConfig) string { for _, contact := range smsCfg.Contacts { - if contact.Name == name { + if common.StringToUpper(contact.Name) == common.StringToUpper(name) { return contact.Number } } @@ -53,6 +54,10 @@ func SMSGetNumberByName(name string, smsCfg config.SMSGlobalConfig) string { // SMSNotify sends a message to an individual contact func SMSNotify(to, message string, cfg config.Config) error { + if flag.Lookup("test.v") != nil { + return nil + } + values := url.Values{} values.Set("action", "sendsms") values.Set("user", cfg.SMS.Username) diff --git a/ticker_routes.go b/ticker_routes.go deleted file mode 100644 index 7c49949c..00000000 --- a/ticker_routes.go +++ /dev/null @@ -1,139 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "net/http" - - "github.com/gorilla/mux" - "github.com/thrasher-/gocryptotrader/currency/pair" - exchange "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" -) - -func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) { - var specificTicker ticker.Price - var err error - for i := 0; i < len(bot.exchanges); i++ { - if bot.exchanges[i] != nil { - if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName { - specificTicker, err = bot.exchanges[i].GetTickerPrice( - pair.NewCurrencyPairFromString(currency), - assetType, - ) - break - } - } - } - return specificTicker, err -} - -func jsonTickerResponse(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - currency := vars["currency"] - exchange := vars["exchangeName"] - assetType := vars["assetType"] - - if assetType == "" { - assetType = ticker.Spot - } - response, err := GetSpecificTicker(currency, exchange, assetType) - if err != nil { - log.Printf("Failed to fetch ticker for %s currency: %s\n", exchange, - currency) - return - } - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } -} - -// AllEnabledExchangeCurrencies holds the enabled exchange currencies -type AllEnabledExchangeCurrencies struct { - Data []EnabledExchangeCurrencies `json:"data"` -} - -// EnabledExchangeCurrencies is a sub type for singular exchanges and respective -// currencies -type EnabledExchangeCurrencies struct { - ExchangeName string `json:"exchangeName"` - ExchangeValues []ticker.Price `json:"exchangeValues"` -} - -func GetAllActiveTickers() []EnabledExchangeCurrencies { - var tickerData []EnabledExchangeCurrencies - - for _, individualBot := range bot.exchanges { - if individualBot != nil && individualBot.IsEnabled() { - var individualExchange EnabledExchangeCurrencies - exchangeName := individualBot.GetName() - individualExchange.ExchangeName = exchangeName - log.Println( - "Getting enabled currencies for '" + exchangeName + "'", - ) - currencies := individualBot.GetEnabledCurrencies() - for _, x := range currencies { - currency := x - assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName) - if err != nil { - log.Printf("failed to get %s exchange asset types. Error: %s", - exchangeName, err) - continue - } - var tickerPrice ticker.Price - if len(assetTypes) > 1 { - for y := range assetTypes { - tickerPrice, err = individualBot.UpdateTicker(currency, - assetTypes[y]) - } - } else { - tickerPrice, err = individualBot.UpdateTicker(currency, - assetTypes[0]) - } - - if err != nil { - log.Printf("failed to get %s %s ticker. Error: %s", - currency.Pair().String(), - exchangeName, - err) - continue - } - - individualExchange.ExchangeValues = append( - individualExchange.ExchangeValues, tickerPrice, - ) - } - tickerData = append(tickerData, individualExchange) - } - } - return tickerData -} - -func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) { - var response AllEnabledExchangeCurrencies - response.Data = GetAllActiveTickers() - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } -} - -// ExchangeRoutes denotes the current exchange routes -var ExchangeRoutes = Routes{ - Route{ - "AllActiveExchangesAndCurrencies", - "GET", - "/exchanges/enabled/latest/all", - getAllActiveTickersResponse, - }, - Route{ - "IndividualExchangeAndCurrency", - "GET", - "/exchanges/{exchangeName}/latest/{currency}", - jsonTickerResponse, - }, -} diff --git a/ticker_routes_test.go b/ticker_routes_test.go deleted file mode 100644 index 06ab7d0f..00000000 --- a/ticker_routes_test.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/tools/websocket_client/main.go b/tools/websocket_client/main.go index 509ada13..66e23569 100644 --- a/tools/websocket_client/main.go +++ b/tools/websocket_client/main.go @@ -7,6 +7,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" ) var ( @@ -74,7 +75,7 @@ func main() { log.Println("Connected to websocket!") log.Println("Authenticating..") - SendWebsocketAuth("username", "password") + SendWebsocketAuth("blah", "blah") var wsResp WebsocketEventResponse err = WSConn.ReadJSON(&wsResp) @@ -112,9 +113,22 @@ func main() { log.Printf("Fetched config.") + dataJSON, err := common.JSONEncode(&wsResp.Data) + if err != nil { + log.Fatal(err) + } + + var resultCfg config.Config + err = common.JSONDecode(dataJSON, &resultCfg) + if err != nil { + log.Fatal(err) + } + + resultCfg.Name = "TEST" + req = WebsocketEvent{ Event: "SaveConfig", - Data: wsResp.Data, + Data: resultCfg, } log.Println("Saving config..") diff --git a/wallet_routes.go b/wallet_routes.go deleted file mode 100644 index 3ed2e1f3..00000000 --- a/wallet_routes.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "log" - "net/http" - - "github.com/thrasher-/gocryptotrader/exchanges" -) - -// AllEnabledExchangeAccounts holds all enabled accounts info -type AllEnabledExchangeAccounts struct { - Data []exchange.AccountInfo `json:"data"` -} - -// GetCollatedExchangeAccountInfoByCoin collates individual exchange account -// information and turns into into a map string of -// exchange.AccountCurrencyInfo -func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo { - result := make(map[string]exchange.AccountCurrencyInfo) - for i := 0; i < len(accounts); i++ { - for j := 0; j < len(accounts[i].Currencies); j++ { - currencyName := accounts[i].Currencies[j].CurrencyName - avail := accounts[i].Currencies[j].TotalValue - onHold := accounts[i].Currencies[j].Hold - - info, ok := result[currencyName] - if !ok { - accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail} - result[currencyName] = accountInfo - } else { - info.Hold += onHold - info.TotalValue += avail - result[currencyName] = info - } - } - } - return result -} - -// GetAccountCurrencyInfoByExchangeName returns info for an exchange -func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) { - for i := 0; i < len(accounts); i++ { - if accounts[i].ExchangeName == exchangeName { - return accounts[i], nil - } - } - return exchange.AccountInfo{}, errors.New(exchange.ErrExchangeNotFound) -} - -// GetAllEnabledExchangeAccountInfo returns all the current enabled exchanges -func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts { - var response AllEnabledExchangeAccounts - for _, individualBot := range bot.exchanges { - if individualBot != nil && individualBot.IsEnabled() { - if !individualBot.GetAuthenticatedAPISupport() { - log.Printf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName()) - continue - } - individualExchange, err := individualBot.GetExchangeAccountInfo() - if err != nil { - log.Printf("Error encountered retrieving exchange account info for %s. Error %s", - individualBot.GetName(), err) - continue - } - response.Data = append(response.Data, individualExchange) - } - } - return response -} - -// SendAllEnabledAccountInfo via get request returns JSON response of account -// info -func SendAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) { - response := GetAllEnabledExchangeAccountInfo() - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(response); err != nil { - panic(err) - } -} - -// WalletRoutes are current routes specified for queries. -var WalletRoutes = Routes{ - Route{ - "AllEnabledAccountInfo", - "GET", - "/exchanges/enabled/accounts/all", - SendAllEnabledAccountInfo, - }, -} diff --git a/wallet_routes_test.go b/wallet_routes_test.go deleted file mode 100644 index b8f5d9b9..00000000 --- a/wallet_routes_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "testing" -) - -func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { - GetCollatedExchangeAccountInfoByCoin(GetAllEnabledExchangeAccountInfo().Data) -} - -func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) { - _, err := GetAccountCurrencyInfoByExchangeName( - GetAllEnabledExchangeAccountInfo().Data, "ANX", - ) - if err == nil { - t.Error( - "Test Failed - Wallet_Routes_Test.go - GetAccountCurrencyInfoByExchangeName", - ) - } -} - -func TestGetAllEnabledExchangeAccountInfo(t *testing.T) { - if value := GetAllEnabledExchangeAccountInfo(); len(value.Data) != 0 { - t.Error( - "Test Failed - Wallet_Routes_Test.go - GetAllEnabledExchangeAccountInfo", - ) - } -} diff --git a/websocket.go b/websocket.go index 8c697e57..fb3bb8dc 100644 --- a/websocket.go +++ b/websocket.go @@ -9,12 +9,15 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/currency" ) +// Const vars for websocket const ( WebsocketResponseSuccess = "OK" ) +// WebsocketRoutes adds ws route to the HTTP server var WebsocketRoutes = Routes{ Route{ "ws", @@ -24,6 +27,7 @@ var WebsocketRoutes = Routes{ }, } +// WebsocketClient stores information related to the websocket client type WebsocketClient struct { ID int Conn *websocket.Conn @@ -31,6 +35,7 @@ type WebsocketClient struct { Authenticated bool } +// WebsocketEvent is the struct used for websocket events type WebsocketEvent struct { Exchange string `json:"exchange,omitempty"` AssetType string `json:"assetType,omitempty"` @@ -38,20 +43,26 @@ type WebsocketEvent struct { Data interface{} } +// WebsocketEventResponse is the struct used for websocket event responses type WebsocketEventResponse struct { Event string `json:"event"` Data interface{} `json:"data"` Error string `json:"error"` } -type WebsocketTickerRequest struct { +// WebsocketOrderbookTickerRequest is a struct used for ticker and orderbook +// requests +type WebsocketOrderbookTickerRequest struct { Exchange string `json:"exchangeName"` Currency string `json:"currency"` AssetType string `json:"assetType"` } +// WebsocketClientHub stores an array of websocket clients var WebsocketClientHub []WebsocketClient +// WebsocketClientHandler upgrades the HTTP connection to a websocket +// compatible one func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{ WriteBufferSize: 1024, @@ -73,6 +84,7 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { log.Println("New websocket client connected.") } +// DisconnectWebsocketClient disconnects a websocket client func DisconnectWebsocketClient(id int, err error) { for i := range WebsocketClientHub { if WebsocketClientHub[i].ID == id { @@ -84,6 +96,7 @@ func DisconnectWebsocketClient(id int, err error) { } } +// SendWebsocketMessage sends a websocket message to a specific client func SendWebsocketMessage(id int, data interface{}) error { for _, x := range WebsocketClientHub { if x.ID == id { @@ -93,6 +106,8 @@ func SendWebsocketMessage(id int, data interface{}) error { return nil } +// BroadcastWebsocketMessage broadcasts a websocket event message to all +// websocket clients func BroadcastWebsocketMessage(evt WebsocketEvent) error { for _, x := range WebsocketClientHub { x.Conn.WriteJSON(evt) @@ -100,29 +115,163 @@ func BroadcastWebsocketMessage(evt WebsocketEvent) error { return nil } +// WebsocketAuth is a struct used for type WebsocketAuth struct { Username string `json:"username"` Password string `json:"password"` } +type wsCommandHandler func(wsClient *websocket.Conn, data interface{}) error + +var wsHandlers = map[string]wsCommandHandler{ + "getconfig": wsGetConfig, + "saveconfig": wsSaveConfig, + "getaccountinfo": wsGetAccountInfo, + "gettickers": wsGetTickers, + "getticker": wsGetTicker, + "getorderbooks": wsGetOrderbooks, + "getorderbook": wsGetOrderbook, + "getexchangerates": wsGetExchangeRates, + "getportfolio": wsGetPortfolio, +} + +func wsGetConfig(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetConfig", + Data: bot.config, + } + return wsClient.WriteJSON(wsResp) +} + +func wsSaveConfig(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "SaveConfig", + } + var cfg config.Config + err := common.JSONDecode(data.([]byte), &cfg) + if err != nil { + wsResp.Error = err.Error() + err = wsClient.WriteJSON(wsResp) + if err != nil { + return err + } + } + + err = bot.config.UpdateConfig(bot.configFile, cfg) + if err != nil { + wsResp.Error = err.Error() + err = wsClient.WriteJSON(wsResp) + if err != nil { + return err + } + } + + setupBotExchanges() + wsResp.Data = WebsocketResponseSuccess + return wsClient.WriteJSON(wsResp) +} + +func wsGetAccountInfo(wsClient *websocket.Conn, data interface{}) error { + accountInfo := GetAllEnabledExchangeAccountInfo() + wsResp := WebsocketEventResponse{ + Event: "GetAccountInfo", + Data: accountInfo, + } + return wsClient.WriteJSON(wsResp) +} + +func wsGetTickers(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetTickers", + } + wsResp.Data = GetAllActiveTickers() + return wsClient.WriteJSON(wsResp) +} + +func wsGetTicker(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetTicker", + } + var tickerReq WebsocketOrderbookTickerRequest + err := common.JSONDecode(data.([]byte), &tickerReq) + if err != nil { + wsResp.Error = err.Error() + wsClient.WriteJSON(wsResp) + return err + } + + result, err := GetSpecificTicker(tickerReq.Currency, + tickerReq.Exchange, tickerReq.AssetType) + + if err != nil { + wsResp.Error = err.Error() + wsClient.WriteJSON(wsResp) + return err + } + wsResp.Data = result + return wsClient.WriteJSON(wsResp) +} + +func wsGetOrderbooks(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetOrderbooks", + } + wsResp.Data = GetAllActiveOrderbooks() + return wsClient.WriteJSON(wsResp) +} + +func wsGetOrderbook(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetOrderbook", + } + var orderbookReq WebsocketOrderbookTickerRequest + err := common.JSONDecode(data.([]byte), &orderbookReq) + if err != nil { + wsResp.Error = err.Error() + wsClient.WriteJSON(wsResp) + return err + } + + result, err := GetSpecificOrderbook(orderbookReq.Currency, + orderbookReq.Exchange, orderbookReq.AssetType) + + if err != nil { + wsResp.Error = err.Error() + wsClient.WriteJSON(wsResp) + return err + } + wsResp.Data = result + return wsClient.WriteJSON(wsResp) +} + +func wsGetExchangeRates(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetExchangeRates", + } + if currency.YahooEnabled { + wsResp.Data = currency.CurrencyStore + } else { + wsResp.Data = currency.CurrencyStoreFixer + } + return wsClient.WriteJSON(wsResp) +} + +func wsGetPortfolio(wsClient *websocket.Conn, data interface{}) error { + wsResp := WebsocketEventResponse{ + Event: "GetPortfolio", + } + wsResp.Data = bot.portfolio.GetPortfolioSummary() + return wsClient.WriteJSON(wsResp) +} + +// WebsocketHandler Handles websocket client requests func WebsocketHandler() { for { for x := range WebsocketClientHub { - msgType, msg, err := WebsocketClientHub[x].Conn.ReadMessage() - if err != nil { - DisconnectWebsocketClient(x, err) - continue - } - - if msgType != websocket.TextMessage { - DisconnectWebsocketClient(x, err) - continue - } - var evt WebsocketEvent - err = common.JSONDecode(msg, &evt) + err := WebsocketClientHub[x].Conn.ReadJSON(&evt) if err != nil { - log.Println(err) + DisconnectWebsocketClient(x, err) continue } @@ -137,6 +286,9 @@ func WebsocketHandler() { continue } + req := common.StringToLower(evt.Event) + log.Printf("Websocket req: %s", req) + if !WebsocketClientHub[x].Authenticated && evt.Event != "auth" { wsResp := WebsocketEventResponse{ Event: "auth", @@ -152,8 +304,8 @@ func WebsocketHandler() { log.Println(err) continue } - hashPW := common.HexEncodeToString(common.GetSHA256([]byte("password"))) - if auth.Username == "username" && auth.Password == hashPW { + hashPW := common.HexEncodeToString(common.GetSHA256([]byte(bot.config.Webserver.AdminUsername))) + if auth.Username == bot.config.Webserver.AdminUsername && auth.Password == hashPW { WebsocketClientHub[x].Authenticated = true wsResp := WebsocketEventResponse{ Event: "auth", @@ -172,97 +324,15 @@ func WebsocketHandler() { continue } } - switch evt.Event { - case "GetConfig": - wsResp := WebsocketEventResponse{ - Event: "GetConfig", - Data: bot.config, - } - SendWebsocketMessage(x, wsResp) + result, ok := wsHandlers[req] + if !ok { + log.Printf("Websocket unsupported event") continue - case "SaveConfig": - wsResp := WebsocketEventResponse{ - Event: "SaveConfig", - } - var cfg config.Config - err := common.JSONDecode(dataJSON, &cfg) - if err != nil { - wsResp.Error = err.Error() - SendWebsocketMessage(x, wsResp) - log.Println(err) - continue - } + } - //Save change the settings - for x := range bot.config.Exchanges { - for i := 0; i < len(cfg.Exchanges); i++ { - if cfg.Exchanges[i].Name == bot.config.Exchanges[x].Name { - bot.config.Exchanges[x].Enabled = cfg.Exchanges[i].Enabled - bot.config.Exchanges[x].APIKey = cfg.Exchanges[i].APIKey - bot.config.Exchanges[x].APISecret = cfg.Exchanges[i].APISecret - bot.config.Exchanges[x].EnabledPairs = cfg.Exchanges[i].EnabledPairs - } - } - } - - //Reload the configuration - err = bot.config.SaveConfig(bot.configFile) - if err != nil { - wsResp.Error = err.Error() - SendWebsocketMessage(x, wsResp) - continue - } - err = bot.config.LoadConfig(bot.configFile) - if err != nil { - wsResp.Error = err.Error() - SendWebsocketMessage(x, wsResp) - continue - } - setupBotExchanges() - wsResp.Data = WebsocketResponseSuccess - SendWebsocketMessage(x, wsResp) - continue - case "GetAccountInfo": - accountInfo := GetAllEnabledExchangeAccountInfo() - wsResp := WebsocketEventResponse{ - Event: "GetAccountInfo", - Data: accountInfo, - } - SendWebsocketMessage(x, wsResp) - continue - case "GetTicker": - wsResp := WebsocketEventResponse{ - Event: "GetTicker", - } - var tickerReq WebsocketTickerRequest - err := common.JSONDecode(dataJSON, &tickerReq) - if err != nil { - wsResp.Error = err.Error() - SendWebsocketMessage(x, wsResp) - log.Println(err) - continue - } - - data, err := GetSpecificTicker(tickerReq.Currency, - tickerReq.Exchange, tickerReq.AssetType) - - if err != nil { - wsResp.Error = err.Error() - SendWebsocketMessage(x, wsResp) - log.Println(err) - continue - } - wsResp.Data = data - SendWebsocketMessage(x, wsResp) - continue - - case "GetTickers": - wsResp := WebsocketEventResponse{ - Event: "GetTickers", - } - tickers := GetAllActiveTickers() - wsResp.Data = tickers - SendWebsocketMessage(x, wsResp) + err = result(WebsocketClientHub[x].Conn, dataJSON) + if err != nil { + log.Printf("Websocket request %s failed. Error %s", evt.Event, err) continue } } From 3ad9c3dfbe8f878a745ac05a50bfb06c903af61d Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 6 Sep 2017 15:36:06 +1000 Subject: [PATCH 18/32] Use cryptoID instead of blockr (currently down) --- portfolio/portfolio.go | 126 +++++++++--------------------------- portfolio/portfolio_test.go | 103 +++++++++++------------------ 2 files changed, 71 insertions(+), 158 deletions(-) diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index ddff2170..81f6eae4 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -10,9 +10,7 @@ import ( ) const ( - blockrAPIURL = "blockr.io/api" - blockrAPIVersion = "1" - blockrAddressBalance = "address/balance" + cryptoIDAPIURL = "https://chainz.cryptoid.info" etherchainAPIURL = "https://etherchain.org/api" etherchainAccountMultiple = "account/multiple" @@ -38,32 +36,6 @@ type Address struct { Description string } -// BlockrAddress holds JSON incoming and outgoing data for BLOCKR with address -// information -type BlockrAddress struct { - Address string `json:"address"` - Balance float64 `json:"balance"` - BalanceMultisig float64 `json:"balance_multisig"` -} - -// BlockrAddressBalanceSingle holds JSON incoming and outgoing data for BLOCKR -// with address balance information -type BlockrAddressBalanceSingle struct { - Status string `json:"status"` - Data BlockrAddress `json:"data"` - Code int `json:"code"` - Message string `json:"message"` -} - -// BlockrAddressBalanceMulti holds JSON incoming and outgoing data for BLOCKR -// with address balance information for multiple wallets -type BlockrAddressBalanceMulti struct { - Status string `json:"status"` - Data []BlockrAddress `json:"data"` - Code int `json:"code"` - Message string `json:"message"` -} - // EtherchainBalanceResponse holds JSON incoming and outgoing data for // Etherchain type EtherchainBalanceResponse struct { @@ -118,56 +90,21 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } -// GetBlockrBalanceSingle queries Blockr for an address balance for either a -// LTC or a BTC single address -func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { - valid, _ := common.IsValidCryptoAddress(address, coinType) - if !valid { - return BlockrAddressBalanceSingle{}, fmt.Errorf( - "Not a %s address", common.StringToUpper(coinType), - ) +// GetCryptoIDAddress queries CryptoID for an address balance for a +// specified cryptocurrency +func GetCryptoIDAddress(address string, coinType string) (float64, error) { + ok, err := common.IsValidCryptoAddress(address, coinType) + if !ok || err != nil { + return 0, errors.New("invalid address") } - url := fmt.Sprintf( - "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, - blockrAPIVersion, blockrAddressBalance, address, - ) - result := BlockrAddressBalanceSingle{} - err := common.SendHTTPGetRequest(url, true, &result) + var result interface{} + url := fmt.Sprintf("%s/%s/api.dws?q=getbalance&a=%s", cryptoIDAPIURL, common.StringToLower(coinType), address) + err = common.SendHTTPGetRequest(url, true, &result) if err != nil { - return result, err + return 0, err } - if result.Status != "success" { - return result, errors.New(result.Message) - } - return result, nil -} - -// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC -// or a BTC multiple addresses -func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { - for _, add := range addresses { - valid, _ := common.IsValidCryptoAddress(add, coinType) - if !valid { - return BlockrAddressBalanceMulti{}, fmt.Errorf( - "Not a %s address", common.StringToUpper(coinType), - ) - } - } - addressesStr := common.JoinStrings(addresses, ",") - url := fmt.Sprintf( - "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, - blockrAPIVersion, blockrAddressBalance, addressesStr, - ) - result := BlockrAddressBalanceMulti{} - err := common.SendHTTPGetRequest(url, true, &result) - if err != nil { - return result, err - } - if result.Status != "success" { - return result, errors.New(result.Message) - } - return result, nil + return result.(float64), nil } // GetAddressBalance acceses the portfolio base and returns the balance by passed @@ -213,6 +150,18 @@ func (p *Base) ExchangeAddressExists(exchangeName, coinType string) bool { return false } +// AddExchangeAddress adds an exchange address to the portfolio base +func (p *Base) AddExchangeAddress(exchangeName, coinType string, balance float64) { + if p.ExchangeAddressExists(exchangeName, coinType) { + p.UpdateExchangeAddressBalance(exchangeName, coinType, balance) + } else { + p.Addresses = append( + p.Addresses, Address{Address: exchangeName, CoinType: coinType, + Balance: balance, Description: PortfolioAddressExchange}, + ) + } +} + // UpdateAddressBalance updates the portfolio base balance func (p *Base) UpdateAddressBalance(address string, amount float64) { for x := range p.Addresses { @@ -244,6 +193,10 @@ func (p *Base) UpdateExchangeAddressBalance(exchangeName, coinType string, balan // AddAddress adds an address to the portfolio base func (p *Base) AddAddress(address, coinType, description string, balance float64) { + if description == PortfolioAddressExchange { + p.AddExchangeAddress(address, coinType, balance) + return + } if !p.AddressExists(address) { p.Addresses = append( p.Addresses, Address{Address: address, CoinType: coinType, @@ -286,22 +239,12 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool { } return true } - if len(addresses) > 1 { - result, err := GetBlockrAddressMulti(addresses, coinType) + for x := range addresses { + result, err := GetCryptoIDAddress(addresses[x], coinType) if err != nil { return false } - for _, x := range result.Data { - p.AddAddress(x.Address, coinType, PortfolioAddressPersonal, x.Balance) - } - } else { - result, err := GetBlockrBalanceSingle(addresses[0], coinType) - if err != nil { - return false - } - p.AddAddress( - addresses[0], coinType, PortfolioAddressPersonal, result.Data.Balance, - ) + p.AddAddress(addresses[x], coinType, PortfolioAddressPersonal, result) } return true } @@ -413,12 +356,7 @@ func (p *Base) GetPortfolioSummary() Summary { y = y / common.WeiPerEther personalHoldings[x] = y } - balance, ok := totalCoins[x] - if !ok { - totalCoins[x] = y - } else { - totalCoins[x] = y + balance - } + totalCoins[x] = y } for x, y := range exchangeHoldings { diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 3a966481..86e689a0 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -32,62 +32,11 @@ func TestGetEthereumBalance(t *testing.T) { } } -func TestGetBlockrBalanceSingle(t *testing.T) { - litecoinAddress := "LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL" - bitcoinAddress := "3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r" - nonsenseAddress := "DingDong" - ltc := "LtC" - btc := "bTc" - - response, err := GetBlockrBalanceSingle(litecoinAddress, ltc) +func TestGetCryptoIDBalance(t *testing.T) { + ltcAddress := "LX2LMYXtuv5tiYEMztSSoEZcafFPYJFRK1" + _, err := GetCryptoIDAddress(ltcAddress, "ltc") if err != nil { - t.Errorf("Test Failed - Portfolio GetBlockrBalanceSingle() Error: %s", err) - } - response, err = GetBlockrBalanceSingle(litecoinAddress, btc) - if err == nil { - t.Errorf("Test Failed - Portfolio GetBlockrBalanceSingle() Error: %s", err) - } - response, err = GetBlockrBalanceSingle(bitcoinAddress, btc) - if err != nil { - t.Errorf("Test Failed - Portfolio GetBlockrBalanceSingle() Error: %s", err) - } - response, err = GetBlockrBalanceSingle(bitcoinAddress, ltc) - if err != nil { - t.Errorf("Test Failed - Portfolio GetBlockrBalanceSingle() Error: %s", err) - } - response, err = GetBlockrBalanceSingle(nonsenseAddress, ltc+btc) - if err == nil { - t.Errorf("Test Failed - Portfolio GetBlockrBalanceSingle() Error: %s", err) - } - if response.Status == "success" { - t.Error( - "Test Failed - Portfolio GetBlockrBalanceSingle() Error: Incorrect status", - ) - } -} - -func TestGetBlockrAddressMulti(t *testing.T) { - litecoinAddresses := []string{ - "LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "LVa8wZ983PvWtdwXZ8viK6SocMENLCXkEy", - } - bitcoinAddresses := []string{ - "3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r", "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", - } - nonsenseAddresses := []string{"DingDong", "ningNang"} - ltc := "LtC" - btc := "bTc" - - _, err := GetBlockrAddressMulti(litecoinAddresses, ltc) - if err != nil { - t.Errorf("Test Failed - Portfolio GetBlockrAddressMulti() Error: %s", err) - } - _, err = GetBlockrAddressMulti(bitcoinAddresses, btc) - if err != nil { - t.Errorf("Test Failed - Portfolio GetBlockrAddressMulti() Error: %s", err) - } - _, err = GetBlockrAddressMulti(nonsenseAddresses, ltc) - if err == nil { - t.Errorf("Test Failed - Portfolio GetBlockrAddressMulti() Error") + t.Fatalf("Test failed. TestGetCryptoIDBalance error: %s", err) } } @@ -148,6 +97,16 @@ func TestExchangeAddressExists(t *testing.T) { } +func TestAddExchangeAddress(t *testing.T) { + newbase := Base{} + newbase.AddExchangeAddress("ANX", "BTC", 100) + newbase.AddExchangeAddress("ANX", "BTC", 200) + + if !newbase.ExchangeAddressExists("ANX", "BTC") { + t.Error("Test Failed - TestExchangeAddressExists address doesn't exist") + } +} + func TestUpdateAddressBalance(t *testing.T) { newbase := Base{} newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) @@ -178,7 +137,7 @@ func TestRemoveExchangeAddress(t *testing.T) { exchangeName := "BallerExchange" coinType := "LTC" - newbase.AddAddress(exchangeName, coinType, PortfolioAddressExchange, 420) + newbase.AddExchangeAddress(exchangeName, coinType, 420) if !newbase.ExchangeAddressExists(exchangeName, coinType) { t.Error("Test failed - portfolio_test.go - TestRemoveAddress") @@ -192,7 +151,7 @@ func TestRemoveExchangeAddress(t *testing.T) { func TestUpdateExchangeAddressBalance(t *testing.T) { newbase := Base{} - newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + newbase.AddExchangeAddress("someaddress", "LTC", 0.02) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) portfolio.UpdateExchangeAddressBalance("someaddress", "LTC", 0.04) @@ -273,8 +232,8 @@ func TestUpdatePortfolio(t *testing.T) { func TestGetPortfolioByExchange(t *testing.T) { newbase := Base{} - newbase.AddAddress("ANX", "LTC", PortfolioAddressExchange, 0.07) - newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 0.05) + newbase.AddExchangeAddress("ANX", "LTC", 0.07) + newbase.AddExchangeAddress("Bitfinex", "LTC", 0.05) newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 0.03) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) @@ -340,15 +299,17 @@ func TestGetPortfolioSummary(t *testing.T) { newbase := Base{} // Personal holdings newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 1) + newbase.AddAddress("someaddress2", "LTC", PortfolioAddressPersonal, 2) + newbase.AddAddress("someaddress3", "BTC", PortfolioAddressPersonal, 100) newbase.AddAddress("0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", "ETH", PortfolioAddressPersonal, 865346880000000000) newbase.AddAddress("0x9edc81c813b26165f607a8d1b8db87a02f34307f", "ETH", PortfolioAddressPersonal, 165346880000000000) // Exchange holdings - newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 20) - newbase.AddAddress("Bitfinex", "BTC", PortfolioAddressExchange, 100) - newbase.AddAddress("ANX", "ETH", PortfolioAddressExchange, 42) + newbase.AddExchangeAddress("Bitfinex", "LTC", 20) + newbase.AddExchangeAddress("Bitfinex", "BTC", 100) + newbase.AddExchangeAddress("ANX", "ETH", 42) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) @@ -371,7 +332,11 @@ func TestGetPortfolioSummary(t *testing.T) { t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") } - if getTotalsVal("LTC").Balance != 101 { + if getTotalsVal("LTC").Balance != 23 { + t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + } + + if getTotalsVal("BTC").Balance != 200 { t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") } } @@ -400,7 +365,17 @@ func TestSeedPortfolio(t *testing.T) { } func TestStartPortfolioWatcher(t *testing.T) { - //Not until testTimeoutFeature and errors + newBase := Base{} + newBase.AddAddress("LX2LMYXtuv5tiYEMztSSoEZcafFPYJFRK1", "LTC", PortfolioAddressPersonal, 0.02) + newBase.AddAddress("Testy", "LTC", PortfolioAddressPersonal, 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newBase) + + if !portfolio.AddressExists("LX2LMYXtuv5tiYEMztSSoEZcafFPYJFRK1") { + t.Error("Test Failed - portfolio_test.go - TestStartPortfolioWatcher") + } + + go StartPortfolioWatcher() } func TestGetPortfolio(t *testing.T) { From 0682dcec8837e372ec3bef05c59ebcb939d8644a Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Sun, 10 Sep 2017 16:28:16 +1000 Subject: [PATCH 19/32] Add currency converter provider failover and add config setting --- config/config.go | 27 +++++-- currency/currency.go | 31 +++++++- currency/currency_test.go | 159 +++++++++++++++++++++++++------------- main.go | 16 +++- 4 files changed, 168 insertions(+), 65 deletions(-) diff --git a/config/config.go b/config/config.go index 2d01c15a..2e877c36 100644 --- a/config/config.go +++ b/config/config.go @@ -45,6 +45,7 @@ var ( WarningWebserverListenAddressInvalid = "WARNING -- Webserver support disabled due to invalid listen address." WarningWebserverRootWebFolderNotFound = "WARNING -- Webserver support disabled due to missing web folder." WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values." + WarningCurrencyExchangeProvider = "WARNING -- Currency exchange provider invalid valid. Reset to Fixer." RenamingConfigFile = "Renaming config file %s to %s." Cfg Config ) @@ -86,14 +87,15 @@ type CurrencyPairFormatConfig struct { // Config is the overarching object that holds all the information for // prestart management of portfolio, SMSGlobal, webserver and enabled exchange type Config struct { - Name string - EncryptConfig int - Cryptocurrencies string - CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"` - Portfolio portfolio.Base `json:"PortfolioAddresses"` - SMS SMSGlobalConfig `json:"SMSGlobal"` - Webserver WebserverConfig `json:"Webserver"` - Exchanges []ExchangeConfig `json:"Exchanges"` + Name string + EncryptConfig int + Cryptocurrencies string + CurrencyExchangeProvider string + CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"` + Portfolio portfolio.Base `json:"PortfolioAddresses"` + SMS SMSGlobalConfig `json:"SMSGlobal"` + Webserver WebserverConfig `json:"Webserver"` + Exchanges []ExchangeConfig `json:"Exchanges"` } // ExchangeConfig holds all the information needed for each enabled Exchange. @@ -429,6 +431,15 @@ func (c *Config) LoadConfig(configPath string) error { } } + if c.CurrencyExchangeProvider == "" { + c.CurrencyExchangeProvider = "fixer" + } else { + if c.CurrencyExchangeProvider != "yahoo" && c.CurrencyExchangeProvider != "fixer" { + log.Println(WarningCurrencyExchangeProvider) + c.CurrencyExchangeProvider = "fixer" + } + } + if c.CurrencyPairFormat == nil { c.CurrencyPairFormat = &CurrencyPairFormatConfig{ Delimiter: "-", diff --git a/currency/currency.go b/currency/currency.go index fe4a2b5b..79c05d3b 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -68,9 +68,38 @@ var ( ErrCurrencyNotFound = errors.New("unable to find specified currency") ErrQueryingYahoo = errors.New("unable to query Yahoo currency values") ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data") - YahooEnabled = false + YahooEnabled = true ) +// SetProvider sets the currency exchange service used by the currency +// converter +func SetProvider(yahooEnabled bool) { + if yahooEnabled { + YahooEnabled = true + return + } + YahooEnabled = false +} + +// SwapProvider swaps the currency exchange service used by the curency +// converter +func SwapProvider() { + if YahooEnabled { + YahooEnabled = false + return + } + YahooEnabled = true +} + +// GetProvider returns the currency exchange service used by the currency +// converter +func GetProvider() string { + if YahooEnabled { + return "yahoo" + } + return "fixer" +} + // IsDefaultCurrency checks if the currency passed in matches the default // FIAT currency func IsDefaultCurrency(currency string) bool { diff --git a/currency/currency_test.go b/currency/currency_test.go index 3f354e01..bee21e73 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -7,6 +7,65 @@ import ( "github.com/thrasher-/gocryptotrader/common" ) +func TestSetProvider(t *testing.T) { + defaultVal := YahooEnabled + expected := "yahoo" + SetProvider(true) + actual := GetProvider() + if expected != actual { + t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + } + + SetProvider(false) + expected = "fixer" + actual = GetProvider() + if expected != actual { + t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + } + + SetProvider(defaultVal) +} + +func TestSwapProvider(t *testing.T) { + defaultVal := YahooEnabled + expected := "fixer" + SetProvider(true) + SwapProvider() + actual := GetProvider() + if expected != actual { + t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + } + + SetProvider(false) + SwapProvider() + expected = "yahoo" + actual = GetProvider() + if expected != actual { + t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + } + + SetProvider(defaultVal) +} + +func TestGetProvider(t *testing.T) { + defaultVal := YahooEnabled + SetProvider(true) + expected := "yahoo" + actual := GetProvider() + if expected != actual { + t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + } + + SetProvider(false) + expected = "fixer" + actual = GetProvider() + if expected != actual { + t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual) + } + + SetProvider(defaultVal) +} + func TestIsDefaultCurrency(t *testing.T) { t.Parallel() @@ -264,36 +323,35 @@ func TestCheckAndAddCurrency(t *testing.T) { } func TestSeedCurrencyData(t *testing.T) { - if YahooEnabled { - currencyRequestDefault := "" - currencyRequestUSDAUD := "USD,AUD" - currencyRequestObtuse := "WigWham" + SetProvider(true) + currencyRequestDefault := "" + currencyRequestUSDAUD := "USD,AUD" + currencyRequestObtuse := "WigWham" - err := SeedCurrencyData(currencyRequestDefault) - if err != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err, currencyRequestDefault, - ) - } - err2 := SeedCurrencyData(currencyRequestUSDAUD) - if err2 != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err2, currencyRequestUSDAUD, - ) - } - err3 := SeedCurrencyData(currencyRequestObtuse) - if err3 == nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err3, currencyRequestObtuse, - ) - } + err := SeedCurrencyData(currencyRequestDefault) + if err != nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err, currencyRequestDefault, + ) + } + err2 := SeedCurrencyData(currencyRequestUSDAUD) + if err2 != nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err2, currencyRequestUSDAUD, + ) + } + err3 := SeedCurrencyData(currencyRequestObtuse) + if err3 == nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err3, currencyRequestObtuse, + ) } - YahooEnabled = false - err := SeedCurrencyData("") + SetProvider(false) + err = SeedCurrencyData("") if err != nil { t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err) } @@ -313,30 +371,29 @@ func TestMakecurrencyPairs(t *testing.T) { } func TestConvertCurrency(t *testing.T) { - if YahooEnabled { - fiatCurrencies := DefaultCurrencies - for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { - for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { - floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo) - if err != nil { - t.Errorf( - "Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s", - err, floatyMcfloat, currencyFrom, currencyTo, - ) - } - if reflect.TypeOf(floatyMcfloat).String() != "float64" { - t.Error("Test Failed. ConvertCurrency: Error, incorrect return type") - } - if floatyMcfloat <= 0 { - t.Error( - "Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat", - ) - } + SetProvider(true) + fiatCurrencies := DefaultCurrencies + for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { + for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { + floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo) + if err != nil { + t.Errorf( + "Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s", + err, floatyMcfloat, currencyFrom, currencyTo, + ) + } + if reflect.TypeOf(floatyMcfloat).String() != "float64" { + t.Error("Test Failed. ConvertCurrency: Error, incorrect return type") + } + if floatyMcfloat <= 0 { + t.Error( + "Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat", + ) } } } - YahooEnabled = false + SetProvider(false) _, err := ConvertCurrency(1000, "USD", "AUD") if err != nil { t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) @@ -383,10 +440,6 @@ func TestFetchFixerCurrencyData(t *testing.T) { } func TestFetchYahooCurrencyData(t *testing.T) { - if !YahooEnabled { - return - } - t.Parallel() var fetchData []string fiatCurrencies := DefaultCurrencies @@ -407,10 +460,6 @@ func TestFetchYahooCurrencyData(t *testing.T) { } func TestQueryYahooCurrencyValues(t *testing.T) { - if !YahooEnabled { - return - } - err := QueryYahooCurrencyValues(DefaultCurrencies) if err != nil { t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err) diff --git a/main.go b/main.go index 818b2081..a39251bd 100644 --- a/main.go +++ b/main.go @@ -167,10 +167,24 @@ func main() { setupBotExchanges() + if bot.config.CurrencyExchangeProvider == "yahoo" { + currency.SetProvider(true) + } else { + currency.SetProvider(false) + } + + log.Printf("Using %s as currency exchange provider.", bot.config.CurrencyExchangeProvider) + bot.config.RetrieveConfigCurrencyPairs() err = currency.SeedCurrencyData(currency.BaseCurrencies) if err != nil { - log.Fatalf("Fatal error retrieving config currencies. Error: %s", err) + currency.SwapProvider() + log.Printf("'%s' currency exchange provider failed, swapping to %s and testing..", + bot.config.CurrencyExchangeProvider, currency.GetProvider()) + err = currency.SeedCurrencyData(currency.BaseCurrencies) + if err != nil { + log.Fatalf("Fatal error retrieving config currencies. Error: %s", err) + } } log.Println("Successfully retrieved config currencies.") From 6e9bda83a1c8aa168728d1bf75697c4aee244643 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Mon, 11 Sep 2017 09:07:41 +1000 Subject: [PATCH 20/32] Link up websocket handler to routes after refactor and various improvements --- exchanges/stats/stats.go | 10 ++ exchanges/stats/stats_test.go | 14 ++ helpers.go | 3 +- main.go | 12 +- portfolio/portfolio.go | 8 +- portfolio/portfolio_test.go | 4 +- restful_router.go | 22 ++-- tools/websocket_client/main.go | 232 +++++++++++++-------------------- websocket.go | 12 +- 9 files changed, 148 insertions(+), 169 deletions(-) diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index 0b003e2f..200a35fa 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -54,6 +54,16 @@ func Add(exchange string, p pair.CurrencyPair, assetType string, price, volume f return } + if p.FirstCurrency == "XBT" { + newPair := pair.NewCurrencyPair("BTC", p.SecondCurrency.String()) + Append(exchange, newPair, assetType, price, volume) + } + + if p.SecondCurrency == "USDT" { + newPair := pair.NewCurrencyPair(p.FirstCurrency.String(), "USD") + Append(exchange, newPair, assetType, price, volume) + } + Append(exchange, p, assetType, price, volume) } diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index 3cefd2b9..db3c17fa 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -114,6 +114,20 @@ func TestAdd(t *testing.T) { if len(Items) != 1 { t.Error("Test Failed - stats Add did not add exchange info.") } + + p.FirstCurrency = "XBT" + Add("ANX", p, "SPOT", 1201, 43) + + if Items[1].Pair.Pair() != "XBTUSD" { + t.Fatal("Test failed. stats Add did not add exchange info.") + } + + p = pair.NewCurrencyPair("ETH", "USDT") + Add("ANX", p, "SPOT", 300, 1000) + + if Items[2].Pair.Pair() != "ETHUSD" { + t.Fatal("Test failed. stats Add did not add exchange info.") + } } func TestAppend(t *testing.T) { diff --git a/helpers.go b/helpers.go index 554f66ff..86132f6a 100644 --- a/helpers.go +++ b/helpers.go @@ -4,11 +4,10 @@ import ( "errors" "fmt" - "github.com/thrasher-/gocryptotrader/exchanges/stats" - "github.com/thrasher-/gocryptotrader/currency/pair" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) diff --git a/main.go b/main.go index a39251bd..4f8c8aba 100644 --- a/main.go +++ b/main.go @@ -299,9 +299,15 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { currencyName) port.RemoveExchangeAddress(exchangeName, currencyName) } else { - log.Printf("Portfolio: Updating %s %s entry with balance %f.\n", - exchangeName, currencyName, total) - port.UpdateExchangeAddressBalance(exchangeName, currencyName, total) + balance, ok := port.GetAddressBalance(exchangeName, currencyName, portfolio.PortfolioAddressExchange) + if !ok { + continue + } + if balance != total { + log.Printf("Portfolio: Updating %s %s entry with balance %f.\n", + exchangeName, currencyName, total) + port.UpdateExchangeAddressBalance(exchangeName, currencyName, total) + } } } } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index 81f6eae4..d3e52fb5 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -108,10 +108,12 @@ func GetCryptoIDAddress(address string, coinType string) (float64, error) { } // GetAddressBalance acceses the portfolio base and returns the balance by passed -// in address -func (p *Base) GetAddressBalance(address string) (float64, bool) { +// in address, coin type and description +func (p *Base) GetAddressBalance(address, coinType, description string) (float64, bool) { for x := range p.Addresses { - if p.Addresses[x].Address == address { + if p.Addresses[x].Address == address && + p.Addresses[x].Description == description && + p.Addresses[x].CoinType == coinType { return p.Addresses[x].Balance, true } } diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 86e689a0..7a3a9c94 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -49,12 +49,12 @@ func TestGetAddressBalance(t *testing.T) { portfolio := Base{} portfolio.AddAddress(ltcAddress, ltc, description, balance) - addBalance, _ := portfolio.GetAddressBalance("LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL") + addBalance, _ := portfolio.GetAddressBalance("LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", ltc, description) if addBalance != balance { t.Error("Test Failed - Portfolio GetAddressBalance() Error: Incorrect value") } - addBalance, found := portfolio.GetAddressBalance("WigWham") + addBalance, found := portfolio.GetAddressBalance("WigWham", ltc, description) if addBalance != 0 { t.Error("Test Failed - Portfolio GetAddressBalance() Error: Incorrect value") } diff --git a/restful_router.go b/restful_router.go index bd335797..f6666bac 100644 --- a/restful_router.go +++ b/restful_router.go @@ -46,6 +46,12 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { router := mux.NewRouter().StrictSlash(true) routes = Routes{ + Route{ + "", + "GET", + "/", + getIndex, + }, Route{ "GetAllSettings", "GET", @@ -94,6 +100,12 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { "/exchanges/{exchangeName}/orderbook/latest/{currency}", RESTGetOrderbook, }, + Route{ + "ws", + "GET", + "/ws", + WebsocketClientHandler, + }, } for _, route := range routes { @@ -114,13 +126,3 @@ func getIndex(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "GoCryptoTrader RESTful interface. For the web GUI, please visit the web GUI readme.") w.WriteHeader(http.StatusOK) } - -// IndexRoute maps the index route to the getIndex function -var IndexRoute = Routes{ - Route{ - "", - "GET", - "/", - getIndex, - }, -} diff --git a/tools/websocket_client/main.go b/tools/websocket_client/main.go index 66e23569..4cb31fe1 100644 --- a/tools/websocket_client/main.go +++ b/tools/websocket_client/main.go @@ -1,116 +1,114 @@ package main import ( + "errors" + "fmt" "log" "net/http" - "time" "github.com/gorilla/websocket" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" ) +// Vars for the websocket client var ( WSConn *websocket.Conn ) +// WebsocketEvent is the struct used for websocket events type WebsocketEvent struct { - Event string `json:"event"` - Data interface{} `json:"data"` - Exchange string `json:"exchange,omitempty"` + Exchange string `json:"exchange,omitempty"` + AssetType string `json:"assetType,omitempty"` + Event string + Data interface{} } +// WebsocketAuth is the struct used for a websocket auth request type WebsocketAuth struct { Username string `json:"username"` Password string `json:"password"` } +// WebsocketEventResponse is the struct used for websocket event responses type WebsocketEventResponse struct { Event string `json:"event"` Data interface{} `json:"data"` Error string `json:"error"` } -type WebsocketTickerRequest struct { - Exchange string `json:"exchangeName"` - Currency string `json:"currency"` +// WebsocketOrderbookTickerRequest is a struct used for ticker and orderbook +// requests +type WebsocketOrderbookTickerRequest struct { + Exchange string `json:"exchangeName"` + Currency string `json:"currency"` + AssetType string `json:"assetType"` } -func SendWebsocketAuth(username, password string) error { - pwHash := common.HexEncodeToString(common.GetSHA256([]byte(password))) +// SendWebsocketEvent sends a websocket event message +func SendWebsocketEvent(event string, reqData interface{}, result *WebsocketEventResponse) error { req := WebsocketEvent{ - Event: "auth", - Data: WebsocketAuth{ - Username: username, - Password: pwHash, - }, + Event: event, } - return SendWebsocketMsg(req) -} - -func SendWebsocketMsg(data interface{}) error { - return WSConn.WriteJSON(data) -} - -func GetWebsocketTicker(currency string) error { - wsevt := WebsocketEvent{ - Event: "ticker", - Data: currency, + if reqData != nil { + req.Data = reqData } - return SendWebsocketMsg(wsevt) + err := WSConn.WriteJSON(req) + if err != nil { + return err + } + + err = WSConn.ReadJSON(&result) + if err != nil { + return err + } + + if result.Error != "" { + return errors.New(result.Error) + } + + return nil } func main() { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigFile) + if err != nil { + log.Fatalf("Failed to load config file: %s", err) + } + + listenAddr := cfg.Webserver.ListenAddress + wsHost := fmt.Sprintf("ws://%s:%d/ws", common.ExtractHost(listenAddr), + common.ExtractPort(listenAddr)) + log.Printf("Connecting to websocket host: %s", wsHost) + var Dialer websocket.Dialer - var err error - - WSConn, _, err = Dialer.Dial("ws://localhost:9050/ws", http.Header{}) - + WSConn, _, err = Dialer.Dial(wsHost, http.Header{}) if err != nil { log.Println("Unable to connect to websocket server") return } - log.Println("Connected to websocket!") + log.Println("Authenticating..") - SendWebsocketAuth("blah", "blah") - var wsResp WebsocketEventResponse - err = WSConn.ReadJSON(&wsResp) + reqData := WebsocketAuth{ + Username: cfg.Webserver.AdminUsername, + Password: common.HexEncodeToString(common.GetSHA256([]byte(cfg.Webserver.AdminPassword))), + } + err = SendWebsocketEvent("auth", reqData, &wsResp) if err != nil { - log.Println(err) - return + log.Fatal(err) } - - if wsResp.Error != "" { - log.Fatal(wsResp.Error) - } - log.Println("Authenticated successfully") + log.Println("Getting config..") - - req := WebsocketEvent{ - Event: "GetConfig", - } - - err = WSConn.WriteJSON(req) + err = SendWebsocketEvent("GetConfig", nil, &wsResp) if err != nil { - log.Println(err) - return + log.Fatal(err) } - - err = WSConn.ReadJSON(&wsResp) - if err != nil { - log.Println(err) - return - } - - if wsResp.Error != "" { - log.Fatal(wsResp.Error) - } - log.Printf("Fetched config.") dataJSON, err := common.JSONEncode(&wsResp.Data) @@ -124,112 +122,70 @@ func main() { log.Fatal(err) } - resultCfg.Name = "TEST" - - req = WebsocketEvent{ - Event: "SaveConfig", - Data: resultCfg, - } - log.Println("Saving config..") - err = WSConn.WriteJSON(req) + origBotName := resultCfg.Name + resultCfg.Name = "TEST" + err = SendWebsocketEvent("SaveConfig", resultCfg, &wsResp) if err != nil { log.Fatal(err) } - - err = WSConn.ReadJSON(&wsResp) - if err != nil { - log.Println(err) - return - } - - if wsResp.Error != "" { - log.Fatal(wsResp.Error) - } - log.Println("Saved config!") + resultCfg.Name = origBotName + err = SendWebsocketEvent("SaveConfig", resultCfg, &wsResp) + if err != nil { + log.Fatal(err) + } + log.Println("Saved config (restored original bot name)!") + log.Println("Getting account info..") - - req = WebsocketEvent{ - Event: "GetAccountInfo", - } - - err = WSConn.WriteJSON(req) + err = SendWebsocketEvent("GetAccountInfo", nil, &wsResp) if err != nil { - log.Println(err) - return - } - - err = WSConn.ReadJSON(&wsResp) - if err != nil { - log.Println(err) - return - } - - if wsResp.Error != "" { - log.Fatal(wsResp.Error) + log.Fatal(err) } + log.Println("Got account info!") log.Println("Getting tickers..") - - req = WebsocketEvent{ - Event: "GetTickers", - } - - err = WSConn.WriteJSON(req) + err = SendWebsocketEvent("GetTickers", nil, &wsResp) if err != nil { - log.Println(err) - return - } - - err = WSConn.ReadJSON(&wsResp) - if err != nil { - log.Println(err) - return - } - - if wsResp.Error != "" { - log.Fatal(wsResp.Error) + log.Fatal(err) } + log.Println("Got tickers!") log.Println("Getting specific ticker..") - - var tickReq WebsocketTickerRequest - tickReq.Currency = "LTCUSD" - tickReq.Exchange = "Bitfinex" - - req = WebsocketEvent{ - Event: "GetTicker", - Data: tickReq, + dataReq := WebsocketOrderbookTickerRequest{ + Exchange: "Bitfinex", + Currency: "BTCUSD", + AssetType: "SPOT", } - err = WSConn.WriteJSON(req) + err = SendWebsocketEvent("GetTicker", dataReq, &wsResp) if err != nil { - log.Println(err) - return + log.Fatal(err) } + log.Println("Got ticker!") - err = WSConn.ReadJSON(&wsResp) + log.Println("Getting orderbooks..") + err = SendWebsocketEvent("GetOrderbooks", nil, &wsResp) if err != nil { - log.Println(err) - return + log.Fatal(err) } + log.Println("Got orderbooks!") - if wsResp.Error != "" { - log.Fatal(wsResp.Error) + log.Println("Getting specific orderbook..") + err = SendWebsocketEvent("GetOrderbook", dataReq, &wsResp) + if err != nil { + log.Fatal(err) } - - log.Println(wsResp) + log.Println("Got orderbook!") for { - msgType, resp, err := WSConn.ReadMessage() + var wsEvent WebsocketEventResponse + err = WSConn.ReadJSON(&wsEvent) if err != nil { - log.Fatal(err) + break } - log.Println(msgType) - log.Println(string(resp)) + log.Printf("Recv'd: %s", wsEvent.Event) } - time.Sleep(time.Second * 10) WSConn.Close() } diff --git a/websocket.go b/websocket.go index fb3bb8dc..ef11548e 100644 --- a/websocket.go +++ b/websocket.go @@ -17,16 +17,6 @@ const ( WebsocketResponseSuccess = "OK" ) -// WebsocketRoutes adds ws route to the HTTP server -var WebsocketRoutes = Routes{ - Route{ - "ws", - "GET", - "/ws", - WebsocketClientHandler, - }, -} - // WebsocketClient stores information related to the websocket client type WebsocketClient struct { ID int @@ -304,7 +294,7 @@ func WebsocketHandler() { log.Println(err) continue } - hashPW := common.HexEncodeToString(common.GetSHA256([]byte(bot.config.Webserver.AdminUsername))) + hashPW := common.HexEncodeToString(common.GetSHA256([]byte(bot.config.Webserver.AdminPassword))) if auth.Username == bot.config.Webserver.AdminUsername && auth.Password == hashPW { WebsocketClientHub[x].Authenticated = true wsResp := WebsocketEventResponse{ From bc85f11c854975119e22d92516a8280b41d3c7e8 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 12 Sep 2017 14:29:33 +1000 Subject: [PATCH 21/32] Add websocket connection limit, defaults to 1 and can be modified via config Add WebsocketAllowInsecureOrigin config setting, default to false --- config/config.go | 15 +++++++++++---- websocket.go | 20 +++++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index 2e877c36..f59039ae 100644 --- a/config/config.go +++ b/config/config.go @@ -52,10 +52,12 @@ var ( // WebserverConfig struct holds the prestart variables for the webserver. type WebserverConfig struct { - Enabled bool - AdminUsername string - AdminPassword string - ListenAddress string + Enabled bool + AdminUsername string + AdminPassword string + ListenAddress string + WebsocketConnectionLimit int + WebsocketAllowInsecureOrigin bool } // SMSGlobalConfig structure holds all the variables you need for instant @@ -239,6 +241,11 @@ func (c *Config) CheckWebserverConfigValues() error { if port < 1 || port > 65355 { return errors.New(WarningWebserverListenAddressInvalid) } + + if c.Webserver.WebsocketConnectionLimit <= 0 { + c.Webserver.WebsocketConnectionLimit = 1 + } + return nil } diff --git a/websocket.go b/websocket.go index ef11548e..0d93dc56 100644 --- a/websocket.go +++ b/websocket.go @@ -54,11 +54,27 @@ var WebsocketClientHub []WebsocketClient // WebsocketClientHandler upgrades the HTTP connection to a websocket // compatible one func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { + connectionLimit := bot.config.Webserver.WebsocketConnectionLimit + numClients := len(WebsocketClientHub) + + if numClients >= connectionLimit { + log.Printf("Websocket client rejected due to websocket client limit reached. Number of clients %d. Limit %d.", + numClients, connectionLimit) + w.WriteHeader(http.StatusForbidden) + return + } + upgrader := websocket.Upgrader{ WriteBufferSize: 1024, ReadBufferSize: 1024, } + // Allow insecure origin if the Origin request header is present and not + // equal to the Host request header. Default to false + if bot.config.Webserver.WebsocketAllowInsecureOrigin { + upgrader.CheckOrigin = func(r *http.Request) bool { return true } + } + newClient := WebsocketClient{ ID: len(WebsocketClientHub), } @@ -71,7 +87,9 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) { newClient.Conn = conn WebsocketClientHub = append(WebsocketClientHub, newClient) - log.Println("New websocket client connected.") + numClients++ + log.Printf("New websocket client connected. Connected clients: %d. Limit %d.", + numClients, connectionLimit) } // DisconnectWebsocketClient disconnects a websocket client From 04d1de9e22e9a7a936828051c22c4118707fa9b3 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 13 Sep 2017 12:39:49 +1000 Subject: [PATCH 22/32] Add fiat display currency setting, defaults to USD --- config/config.go | 13 ++- currency/symbol/symbol.go | 125 ++++++++++++++++++++++++++++ currency/symbol/symbol_test.go | 21 +++++ main.go | 1 + routines.go | 148 +++++++++++++++++++++++++++------ testdata/configtest.dat | 6 +- tools/portfolio/portfolio.go | 38 ++++++--- 7 files changed, 310 insertions(+), 42 deletions(-) create mode 100644 currency/symbol/symbol.go create mode 100644 currency/symbol/symbol_test.go diff --git a/config/config.go b/config/config.go index f59039ae..322a34df 100644 --- a/config/config.go +++ b/config/config.go @@ -94,10 +94,11 @@ type Config struct { Cryptocurrencies string CurrencyExchangeProvider string CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"` - Portfolio portfolio.Base `json:"PortfolioAddresses"` - SMS SMSGlobalConfig `json:"SMSGlobal"` - Webserver WebserverConfig `json:"Webserver"` - Exchanges []ExchangeConfig `json:"Exchanges"` + FiatDisplayCurrency string + Portfolio portfolio.Base `json:"PortfolioAddresses"` + SMS SMSGlobalConfig `json:"SMSGlobal"` + Webserver WebserverConfig `json:"Webserver"` + Exchanges []ExchangeConfig `json:"Exchanges"` } // ExchangeConfig holds all the information needed for each enabled Exchange. @@ -454,6 +455,10 @@ func (c *Config) LoadConfig(configPath string) error { } } + if c.FiatDisplayCurrency == "" { + c.FiatDisplayCurrency = "USD" + } + return nil } diff --git a/currency/symbol/symbol.go b/currency/symbol/symbol.go new file mode 100644 index 00000000..8f1cefb2 --- /dev/null +++ b/currency/symbol/symbol.go @@ -0,0 +1,125 @@ +package symbol + +import "errors" + +// symbols map holds the currency name and symbol mappings +var symbols = map[string]string{ + "ALL": "Lek", + "AFN": "؋", + "ARS": "$", + "AWG": "ƒ", + "AUD": "$", + "AZN": "ман", + "BSD": "$", + "BBD": "$", + "BYN": "Br", + "BZD": "BZ$", + "BMD": "$", + "BOB": "$b", + "BAM": "KM", + "BWP": "P", + "BGN": "лв", + "BRL": "R$", + "BND": "$", + "KHR": "៛", + "CAD": "$", + "KYD": "$", + "CLP": "$", + "CNY": "¥", + "COP": "$", + "CRC": "₡", + "HRK": "kn", + "CUP": "₱", + "CZK": "Kč", + "DKK": "kr", + "DOP": "RD$", + "XCD": "$", + "EGP": "£", + "SVC": "$", + "EUR": "€", + "FKP": "£", + "FJD": "$", + "GHS": "¢", + "GIP": "£", + "GTQ": "Q", + "GGP": "£", + "GYD": "$", + "HNL": "L", + "HKD": "$", + "HUF": "Ft", + "ISK": "kr", + "INR": "₹", + "IDR": "Rp", + "IRR": "﷼", + "IMP": "£", + "ILS": "₪", + "JMD": "J$", + "JPY": "¥", + "JEP": "£", + "KZT": "лв", + "KPW": "₩", + "KRW": "₩", + "KGS": "лв", + "LAK": "₭", + "LBP": "£", + "LRD": "$", + "MKD": "ден", + "MYR": "RM", + "MUR": "₨", + "MXN": "$", + "MNT": "₮", + "MZN": "MT", + "NAD": "$", + "NPR": "₨", + "ANG": "ƒ", + "NZD": "$", + "NIO": "C$", + "NGN": "₦", + "NOK": "kr", + "OMR": "﷼", + "PKR": "₨", + "PAB": "B/.", + "PYG": "Gs", + "PEN": "S/.", + "PHP": "₱", + "PLN": "zł", + "QAR": "﷼", + "RON": "lei", + "RUB": "₽", + "SHP": "£", + "SAR": "﷼", + "RSD": "Дин.", + "SCR": "₨", + "SGD": "$", + "SBD": "$", + "SOS": "S", + "ZAR": "R", + "LKR": "₨", + "SEK": "kr", + "CHF": "CHF", + "SRD": "$", + "SYP": "£", + "TWD": "NT$", + "THB": "฿", + "TTD": "TT$", + "TRY": "₺", + "TVD": "$", + "UAH": "₴", + "GBP": "£", + "USD": "$", + "UYU": "$U", + "UZS": "лв", + "VEF": "Bs", + "VND": "₫", + "YER": "﷼", + "ZWD": "Z$", +} + +// GetSymbolByCurrencyName returns a currency symbol +func GetSymbolByCurrencyName(currency string) (string, error) { + result, ok := symbols[currency] + if !ok { + return "", errors.New("currency symbol not found") + } + return result, nil +} diff --git a/currency/symbol/symbol_test.go b/currency/symbol/symbol_test.go new file mode 100644 index 00000000..5975cb67 --- /dev/null +++ b/currency/symbol/symbol_test.go @@ -0,0 +1,21 @@ +package symbol + +import "testing" + +func TestGetSymbolByCurrencyName(t *testing.T) { + expected := "₩" + actual, err := GetSymbolByCurrencyName("KPW") + if err != nil { + t.Errorf("Test failed. TestGetSymbolByCurrencyName error: %s", err) + } + + if actual != expected { + t.Errorf("Test failed. TestGetSymbolByCurrencyName differing values") + } + + _, err = GetSymbolByCurrencyName("BLAH") + if err == nil { + t.Errorf("Test failed. TestGetSymbolByCurrencyNam returned nil on non-existant currency") + } + +} diff --git a/main.go b/main.go index 4f8c8aba..67b86f96 100644 --- a/main.go +++ b/main.go @@ -116,6 +116,7 @@ func main() { } log.Printf("Bot '%s' started.\n", bot.config.Name) + log.Printf("Fiat display currency: %s.", bot.config.FiatDisplayCurrency) AdjustGoMaxProcs() if bot.config.SMS.Enabled { diff --git a/routines.go b/routines.go index ae9f7c9d..5c32b6b0 100644 --- a/routines.go +++ b/routines.go @@ -5,16 +5,54 @@ import ( "log" "time" + "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/currency/symbol" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +func printCurrencyFormat(price float64) string { + displaySymbol, err := symbol.GetSymbolByCurrencyName(bot.config.FiatDisplayCurrency) + if err != nil { + log.Printf("Failed to get display symbol: %s", err) + } + + return fmt.Sprintf("%s%.8f", displaySymbol, price) +} + +func printConvertCurrencyFormat(origCurrency string, origPrice float64) string { + displayCurrency := bot.config.FiatDisplayCurrency + conv, err := currency.ConvertCurrency(origPrice, origCurrency, displayCurrency) + if err != nil { + log.Printf("Failed to convert currency: %s", err) + } + + displaySymbol, err := symbol.GetSymbolByCurrencyName(displayCurrency) + if err != nil { + log.Printf("Failed to get display symbol: %s", err) + } + + origSymbol, err := symbol.GetSymbolByCurrencyName(origCurrency) + if err != nil { + log.Printf("Failed to get original currency symbol: %s", err) + } + + return fmt.Sprintf("%s%.2f %s (%s%.2f %s)", + displaySymbol, + conv, + displayCurrency, + origSymbol, + origPrice, + origCurrency, + ) +} + func printSummary(result ticker.Price, p pair.CurrencyPair, assetType, exchangeName string, err error) { if err != nil { - log.Printf("failed to get %s %s ticker. Error: %s", + log.Printf("Failed to get %s %s ticker. Error: %s", p.Pair().String(), exchangeName, err) @@ -22,41 +60,103 @@ func printSummary(result ticker.Price, p pair.CurrencyPair, assetType, exchangeN } stats.Add(exchangeName, p, assetType, result.Last, result.Volume) - log.Printf("%s %s %s: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", - exchangeName, - exchange.FormatCurrency(p).String(), - assetType, - result.Last, - result.Ask, - result.Bid, - result.High, - result.Low, - result.Volume) + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.String() != bot.config.FiatDisplayCurrency { + origCurrency := p.SecondCurrency.Upper().String() + log.Printf("%s %s %s: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + printConvertCurrencyFormat(origCurrency, result.Last), + printConvertCurrencyFormat(origCurrency, result.Ask), + printConvertCurrencyFormat(origCurrency, result.Bid), + printConvertCurrencyFormat(origCurrency, result.High), + printConvertCurrencyFormat(origCurrency, result.Low), + result.Volume) + } else { + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.Upper().String() == bot.config.FiatDisplayCurrency { + log.Printf("%s %s %s: Last %s Ask %s Bid %s High %s Low %s Volume %.8f", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + printCurrencyFormat(result.Last), + printCurrencyFormat(result.Ask), + printCurrencyFormat(result.Bid), + printCurrencyFormat(result.High), + printCurrencyFormat(result.Low), + result.Volume) + } else { + log.Printf("%s %s %s: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + result.Last, + result.Ask, + result.Bid, + result.High, + result.Low, + result.Volume) + } + } } func printOrderbookSummary(result orderbook.Base, p pair.CurrencyPair, assetType, exchangeName string, err error) { if err != nil { - log.Printf("failed to get %s %s orderbook. Error: %s", + log.Printf("Failed to get %s %s orderbook. Error: %s", p.Pair().String(), exchangeName, err) return } - bidsAmount, bidsValue := result.CalculateTotalBids() asksAmount, asksValue := result.CalculateTotalAsks() - log.Printf("%s %s %s: Orderbook Bids len: %d amount: %f total value: %f Asks len: %d amount: %f total value: %f", - exchangeName, - exchange.FormatCurrency(p).String(), - assetType, - len(result.Bids), - bidsAmount, - bidsValue, - len(result.Asks), - asksAmount, - asksValue, - ) + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.String() != bot.config.FiatDisplayCurrency { + origCurrency := p.SecondCurrency.Upper().String() + log.Printf("%s %s %s: Orderbook Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + len(result.Bids), + bidsAmount, + p.FirstCurrency.String(), + printConvertCurrencyFormat(origCurrency, bidsValue), + len(result.Asks), + asksAmount, + p.FirstCurrency.String(), + printConvertCurrencyFormat(origCurrency, asksValue), + ) + } else { + if currency.IsFiatCurrency(p.SecondCurrency.String()) && p.SecondCurrency.Upper().String() == bot.config.FiatDisplayCurrency { + log.Printf("%s %s %s: Orderbook Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + len(result.Bids), + bidsAmount, + p.FirstCurrency.String(), + printCurrencyFormat(bidsValue), + len(result.Asks), + asksAmount, + p.FirstCurrency.String(), + printCurrencyFormat(asksValue), + ) + } else { + log.Printf("%s %s %s: Orderbook Bids len: %d Amount: %f %s. Total value: %f Asks len: %d Amount: %f %s. Total value: %f", + exchangeName, + exchange.FormatCurrency(p).String(), + assetType, + len(result.Bids), + bidsAmount, + p.FirstCurrency.String(), + bidsValue, + len(result.Asks), + asksAmount, + p.FirstCurrency.String(), + asksValue, + ) + } + } + } func relayWebsocketEvent(result interface{}, event, assetType, exchangeName string) { diff --git a/testdata/configtest.dat b/testdata/configtest.dat index e894e05e..46541e00 100644 --- a/testdata/configtest.dat +++ b/testdata/configtest.dat @@ -2,10 +2,12 @@ "Name": "Skynet", "EncryptConfig": 0, "Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", + "CurrencyExchangeProvider": "fixer", "CurrencyPairFormat": { "Uppercase": true, "Delimiter": "-" }, + "FiatDisplayCurrency": "USD", "PortfolioAddresses": { "Addresses": [ { @@ -50,7 +52,9 @@ "Enabled": false, "AdminUsername": "admin", "AdminPassword": "Password", - "ListenAddress": ":9050" + "ListenAddress": ":9050", + "WebsocketConnectionLimit": 1, + "WebsocketAllowInsecureOrigin": false }, "Exchanges": [ { diff --git a/tools/portfolio/portfolio.go b/tools/portfolio/portfolio.go index a7b806fb..bc47dbd1 100644 --- a/tools/portfolio/portfolio.go +++ b/tools/portfolio/portfolio.go @@ -9,22 +9,33 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency" + "github.com/thrasher-/gocryptotrader/currency/symbol" "github.com/thrasher-/gocryptotrader/exchanges/bitfinex" "github.com/thrasher-/gocryptotrader/portfolio" ) var ( - priceMap map[string]float64 + priceMap map[string]float64 + displayCurrency string ) -func printSummary(msg, from, to string, amount float64) { +func printSummary(msg string, amount float64) { log.Println() log.Println(fmt.Sprintf("%s in USD: $%.2f", msg, amount)) - conv, err := currency.ConvertCurrency(amount, "USD", "AUD") - if err != nil { - log.Println(err) - } else { - log.Println(fmt.Sprintf("%s in AUD: $%.2f", msg, conv)) + + if displayCurrency != "USD" { + conv, err := currency.ConvertCurrency(amount, "USD", displayCurrency) + if err != nil { + log.Println(err) + } else { + symb, err := symbol.GetSymbolByCurrencyName(displayCurrency) + if err != nil { + log.Println(fmt.Sprintf("%s in %s: %.2f", msg, displayCurrency, conv)) + } else { + log.Println(fmt.Sprintf("%s in %s: %s%.2f", msg, displayCurrency, symb, conv)) + } + + } } log.Println() } @@ -38,9 +49,9 @@ func getOnlineOfflinePortfolio(coins []portfolio.Coin, online bool) { x.Balance, value, x.Percentage) } if !online { - printSummary("\tOffline balance", "USD", "AUD", totals) + printSummary("\tOffline balance", totals) } else { - printSummary("\tOnline balance", "USD", "AUD", totals) + printSummary("\tOnline balance", totals) } } @@ -53,12 +64,13 @@ func main() { log.Println("GoCryptoTrader: portfolio tool.") var cfg config.Config - var err = cfg.ReadConfig(inFile) + var err = cfg.LoadConfig(inFile) if err != nil { log.Fatal(err) } log.Println("Loaded config file.") + displayCurrency = cfg.FiatDisplayCurrency port := portfolio.Base{} port.SeedPortfolio(cfg.Portfolio) result := port.GetPortfolioSummary() @@ -127,7 +139,7 @@ func main() { for x, y := range portfolioMap { log.Printf("\t%s Amount: %f Subtotal: $%.2f USD (1 %s = $%.2f USD). Percentage of portfolio %.3f%%", x, y.Balance, y.Subtotal, x, y.Subtotal/y.Balance, y.Subtotal/total*100/1) } - printSummary("\tTotal balance", "USD", "AUD", total) + printSummary("\tTotal balance", total) log.Println("OFFLINE COIN TOTALS:") getOnlineOfflinePortfolio(result.Offline, false) @@ -146,7 +158,7 @@ func main() { log.Printf("\t %s Amount: %f Subtotal: $%.2f Coin percentage: %.2f%%\n", y[z].Address, y[z].Balance, value, y[z].Percentage) } - printSummary(fmt.Sprintf("\t %s balance", x), "USD", "AUD", totals) + printSummary(fmt.Sprintf("\t %s balance", x), totals) } log.Println("ONLINE COINS SUMMARY:") @@ -159,6 +171,6 @@ func main() { log.Printf("\t %s Amount: %f Subtotal $%.2f Coin percentage: %.2f%%", z, w.Balance, value, w.Percentage) } - printSummary("\t Exchange balance", "USD", "AUD", totals) + printSummary("\t Exchange balance", totals) } } From 4a67edac99ed85468ca8e3a1c71c398ec94bd78e Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 14 Sep 2017 13:07:08 +1000 Subject: [PATCH 23/32] Expand smsglobal --- config/config.go | 7 +- events/event_test.go | 47 +++++++++- events/events.go | 33 +++---- main.go | 7 +- smsglobal/smsglobal.go | 138 ++++++++++++++++++++++------ smsglobal/smsglobal_test.go | 175 +++++++++++++++++++++++++----------- 6 files changed, 295 insertions(+), 112 deletions(-) diff --git a/config/config.go b/config/config.go index 322a34df..5a156b51 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency" "github.com/thrasher-/gocryptotrader/portfolio" + "github.com/thrasher-/gocryptotrader/smsglobal" ) // Constants declared here are filename strings and test strings @@ -66,11 +67,7 @@ type SMSGlobalConfig struct { Enabled bool Username string Password string - Contacts []struct { - Name string - Number string - Enabled bool - } + Contacts []smsglobal.Contact } // Post holds the bot configuration data diff --git a/events/event_test.go b/events/event_test.go index a57ef5d0..93bf68c0 100644 --- a/events/event_test.go +++ b/events/event_test.go @@ -3,11 +3,31 @@ package events import ( "testing" + "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges/ticker" + "github.com/thrasher-/gocryptotrader/smsglobal" ) +var ( + loaded = false +) + +func testSetup(t *testing.T) { + if !loaded { + cfg := config.GetConfig() + err := cfg.LoadConfig("") + if err != nil { + t.Fatalf("Test failed. Failed to load config %s", err) + } + smsglobal.New(cfg.SMS.Username, cfg.SMS.Password, cfg.Name, cfg.SMS.Contacts) + loaded = true + } +} + func TestAddEvent(t *testing.T) { + testSetup(t) + pair := pair.NewCurrencyPair("BTC", "USD") eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil && eventID != 0 { @@ -36,6 +56,8 @@ func TestAddEvent(t *testing.T) { } func TestRemoveEvent(t *testing.T) { + testSetup(t) + pair := pair.NewCurrencyPair("BTC", "USD") eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil && eventID != 0 { @@ -50,6 +72,8 @@ func TestRemoveEvent(t *testing.T) { } func TestGetEventCounter(t *testing.T) { + testSetup(t) + pair := pair.NewCurrencyPair("BTC", "USD") one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { @@ -87,6 +111,8 @@ func TestGetEventCounter(t *testing.T) { } func TestExecuteAction(t *testing.T) { + testSetup(t) + pair := pair.NewCurrencyPair("BTC", "USD") one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { @@ -127,11 +153,12 @@ func TestExecuteAction(t *testing.T) { if !RemoveEvent(one) { t.Error("Test Failed. ExecuteAction: Error, error removing event") } - // More tests when ExecuteAction is expanded } func TestEventToString(t *testing.T) { + testSetup(t) + pair := pair.NewCurrencyPair("BTC", "USD") one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest) if err != nil { @@ -149,6 +176,8 @@ func TestEventToString(t *testing.T) { } func TestCheckCondition(t *testing.T) { + testSetup(t) + // Test invalid currency pair newPair := pair.NewCurrencyPair("A", "B") one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest) @@ -219,6 +248,8 @@ func TestCheckCondition(t *testing.T) { } func TestIsValidEvent(t *testing.T) { + testSetup(t) + err := IsValidEvent("ANX", "price", ">,==", actionTest) if err != nil { t.Errorf("Test Failed. IsValidEvent: %s", err) @@ -253,6 +284,8 @@ func TestIsValidEvent(t *testing.T) { } func TestCheckEvents(t *testing.T) { + testSetup(t) + pair := pair.NewCurrencyPair("BTC", "USD") _, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest) if err != nil { @@ -263,17 +296,21 @@ func TestCheckEvents(t *testing.T) { } func TestIsValidExchange(t *testing.T) { - boolean := IsValidExchange("ANX", configPathTest) + testSetup(t) + + boolean := IsValidExchange("ANX") if !boolean { t.Error("Test Failed. IsValidExchange: Error, incorrect Exchange") } - boolean = IsValidExchange("OBTUSE", configPathTest) + boolean = IsValidExchange("OBTUSE") if boolean { t.Error("Test Failed. IsValidExchange: Error, incorrect return") } } func TestIsValidCondition(t *testing.T) { + testSetup(t) + boolean := IsValidCondition(">") if !boolean { t.Error("Test Failed. IsValidCondition: Error, incorrect Condition") @@ -301,6 +338,8 @@ func TestIsValidCondition(t *testing.T) { } func TestIsValidAction(t *testing.T) { + testSetup(t) + boolean := IsValidAction("sms") if !boolean { t.Error("Test Failed. IsValidAction: Error, incorrect Action") @@ -316,6 +355,8 @@ func TestIsValidAction(t *testing.T) { } func TestIsValidItem(t *testing.T) { + testSetup(t) + boolean := IsValidItem("price") if !boolean { t.Error("Test Failed. IsValidItem: Error, incorrect Item") diff --git a/events/events.go b/events/events.go index e14a11c3..e6b1a57f 100644 --- a/events/events.go +++ b/events/events.go @@ -23,7 +23,6 @@ const ( actionSMSNotify = "SMS" actionConsolePrint = "CONSOLE_PRINT" actionTest = "ACTION_TEST" - configPathTest = config.ConfigTestFile ) var ( @@ -107,12 +106,12 @@ func (e *Event) ExecuteAction() bool { action := common.SplitStrings(e.Action, ",") if action[0] == actionSMSNotify { message := fmt.Sprintf("Event triggered: %s", e.String()) + s := smsglobal.SMSGlobal if action[1] == "ALL" { - smsglobal.SMSSendToAll(message, config.Cfg) + s.SendMessageToAll(message) } else { - smsglobal.SMSNotify(smsglobal.SMSGetNumberByName(action[1], - config.Cfg.SMS), message, config.Cfg, - ) + contact, _ := s.GetContactByName(action[1]) + s.SendMessage(contact.Number, message) } } } else { @@ -188,12 +187,7 @@ func IsValidEvent(Exchange, Item, Condition, Action string) error { Item = common.StringToUpper(Item) Action = common.StringToUpper(Action) - configPath := "" - if Action == actionTest { - configPath = configPathTest - } - - if !IsValidExchange(Exchange, configPath) { + if !IsValidExchange(Exchange) { return errExchangeDisabled } @@ -218,10 +212,12 @@ func IsValidEvent(Exchange, Item, Condition, Action string) error { return errInvalidAction } - cfg := config.GetConfig() - if action[1] != "ALL" && smsglobal.SMSGetNumberByName( - action[1], cfg.SMS) == smsglobal.ErrSMSContactNotFound { - return errInvalidAction + if action[1] != "ALL" { + s := smsglobal.SMSGlobal + _, err := s.GetContactByName(action[1]) + if err != nil { + return errInvalidAction + } } } else { if Action != actionConsolePrint && Action != actionTest { @@ -254,14 +250,9 @@ func CheckEvents() { } // IsValidExchange validates the exchange -func IsValidExchange(Exchange, configPath string) bool { +func IsValidExchange(Exchange string) bool { Exchange = common.StringToUpper(Exchange) - cfg := config.GetConfig() - if len(cfg.Exchanges) == 0 { - cfg.LoadConfig(configPath) - } - for _, x := range cfg.Exchanges { if x.Name == Exchange && x.Enabled { return true diff --git a/main.go b/main.go index 67b86f96..967b20af 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,7 @@ type ExchangeMain struct { // overarching type across this code base. type Bot struct { config *config.Config + smsglobal *smsglobal.Base portfolio *portfolio.Base exchange ExchangeMain exchanges []exchange.IBotExchange @@ -115,14 +116,16 @@ func main() { log.Fatal(err) } + AdjustGoMaxProcs() log.Printf("Bot '%s' started.\n", bot.config.Name) log.Printf("Fiat display currency: %s.", bot.config.FiatDisplayCurrency) - AdjustGoMaxProcs() if bot.config.SMS.Enabled { + bot.smsglobal = smsglobal.New(bot.config.SMS.Username, bot.config.SMS.Password, + bot.config.Name, bot.config.SMS.Contacts) log.Printf( "SMS support enabled. Number of SMS contacts %d.\n", - smsglobal.GetEnabledSMSContacts(bot.config.SMS), + bot.smsglobal.GetEnabledContacts(), ) } else { log.Println("SMS support disabled.") diff --git a/smsglobal/smsglobal.go b/smsglobal/smsglobal.go index 55fa03ca..d5cc5aab 100644 --- a/smsglobal/smsglobal.go +++ b/smsglobal/smsglobal.go @@ -3,66 +3,150 @@ package smsglobal import ( "errors" "flag" - "log" "net/url" "strings" "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" ) const ( - smsGlobalAPIURL = "http://www.smsglobal.com/http-api.php" + smsGlobalAPIURL = "https://www.smsglobal.com/http-api.php" // ErrSMSContactNotFound is a general error code for "SMS Contact not found." ErrSMSContactNotFound = "SMS Contact not found." errSMSNotSent = "SMS message not sent." ) -// GetEnabledSMSContacts returns how many SMS contacts are enabled in the -// contacts list. -func GetEnabledSMSContacts(smsCfg config.SMSGlobalConfig) int { +// vars for the SMS global package +var ( + SMSGlobal *Base +) + +// Contact struct stores information related to a SMSGlobal contact +type Contact struct { + Name string `json:"name"` + Number string `json:"number"` + Enabled bool `json:"enabled"` +} + +// Base struct stores information related to the SMSGlobal package +type Base struct { + Contacts []Contact `json:"contacts"` + Username string `json:"username"` + Password string `json:"password"` + SendFrom string `json:"send_from"` +} + +// New initalises the SMSGlobal var +func New(username, password, sendFrom string, contacts []Contact) *Base { + if username == "" || password == "" || sendFrom == "" || len(contacts) == 0 { + return nil + } + + var goodContacts []Contact + for x := range contacts { + if contacts[x].Name != "" || contacts[x].Number != "" { + goodContacts = append(goodContacts, contacts[x]) + } + } + + SMSGlobal = &Base{ + Contacts: goodContacts, + Username: username, + Password: password, + SendFrom: sendFrom, + } + return SMSGlobal +} + +// GetEnabledContacts returns how many SMS contacts are enabled in the +// contact list +func (s *Base) GetEnabledContacts() int { counter := 0 - for _, contact := range smsCfg.Contacts { - if contact.Enabled { + for x := range s.Contacts { + if s.Contacts[x].Enabled { counter++ } } return counter } -// SMSSendToAll sends a message to all enabled contacts in cfg -func SMSSendToAll(message string, cfg config.Config) { - for _, contact := range cfg.SMS.Contacts { - if contact.Enabled && len(contact.Number) == 10 { - err := SMSNotify(contact.Number, message, cfg) - if err != nil { - log.Printf("Unable to send SMS to %s.\n", contact.Name) - } +// GetContactByNumber returns a contact with supplied number +func (s *Base) GetContactByNumber(number string) (Contact, error) { + for x := range s.Contacts { + if s.Contacts[x].Number == number { + return s.Contacts[x], nil + } + } + return Contact{}, errors.New(ErrSMSContactNotFound) +} + +// GetContactByName returns a contact with supplied name +func (s *Base) GetContactByName(name string) (Contact, error) { + for x := range s.Contacts { + if common.StringToLower(s.Contacts[x].Name) == common.StringToLower(name) { + return s.Contacts[x], nil + } + } + return Contact{}, errors.New(ErrSMSContactNotFound) +} + +// AddContact checks to see if a contact exists and adds them if it doesn't +func (s *Base) AddContact(contact Contact) { + if contact.Name == "" || contact.Number == "" { + return + } + + if s.ContactExists(contact) { + return + } + + s.Contacts = append(s.Contacts, contact) +} + +// ContactExists checks to see if a contact exists +func (s *Base) ContactExists(contact Contact) bool { + for x := range s.Contacts { + if s.Contacts[x].Number == contact.Number && common.StringToLower(s.Contacts[x].Name) == common.StringToLower(contact.Name) { + return true + } + } + return false +} + +// RemoveContact removes a contact if it exists +func (s *Base) RemoveContact(contact Contact) { + if !s.ContactExists(contact) { + return + } + + for x := range s.Contacts { + if s.Contacts[x].Name == contact.Name && s.Contacts[x].Number == contact.Number { + s.Contacts = append(s.Contacts[:x], s.Contacts[x+1:]...) + return } } } -// SMSGetNumberByName returns contact number by supplied name -func SMSGetNumberByName(name string, smsCfg config.SMSGlobalConfig) string { - for _, contact := range smsCfg.Contacts { - if common.StringToUpper(contact.Name) == common.StringToUpper(name) { - return contact.Number +// SendMessageToAll sends a message to all enabled contacts in cfg +func (s *Base) SendMessageToAll(message string) { + for x := range s.Contacts { + if s.Contacts[x].Enabled { + s.SendMessage(s.Contacts[x].Name, message) } } - return ErrSMSContactNotFound } -// SMSNotify sends a message to an individual contact -func SMSNotify(to, message string, cfg config.Config) error { +// SendMessage sends a message to an individual contact +func (s *Base) SendMessage(to, message string) error { if flag.Lookup("test.v") != nil { return nil } values := url.Values{} values.Set("action", "sendsms") - values.Set("user", cfg.SMS.Username) - values.Set("password", cfg.SMS.Password) - values.Set("from", cfg.Name) + values.Set("user", s.Username) + values.Set("password", s.Password) + values.Set("from", s.SendFrom) values.Set("to", to) values.Set("text", message) diff --git a/smsglobal/smsglobal_test.go b/smsglobal/smsglobal_test.go index 4cd57a17..8fcd287f 100644 --- a/smsglobal/smsglobal_test.go +++ b/smsglobal/smsglobal_test.go @@ -1,71 +1,138 @@ package smsglobal import ( + "log" "testing" - - "github.com/thrasher-/gocryptotrader/config" ) -func TestGetEnabledSMSContacts(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) - if err != nil { - t.Errorf( - "Test Failed. GetEnabledSMSContacts: Function return is incorrect with, %s.", - err, - ) +func TestNew(t *testing.T) { + result := New("", "", "", nil) + if result != nil { + t.Error("Test failed. New: Expected nil result") } - numberOfContacts := GetEnabledSMSContacts(cfg.SMS) - if numberOfContacts != len(cfg.SMS.Contacts) { - t.Errorf( - "Test Failed. GetEnabledSMSContacts: Function return is incorrect with, %d.", - numberOfContacts, - ) + + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + + result = New("bob", "pw", "Skynet", contacts) + if !result.ContactExists(contact) { + t.Error("Test failed. New: Expected contact not found") } } -func TestSMSSendToAll(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) - if err != nil { - t.Errorf( - "Test Failed. SMSSendToAll: \nFunction return is incorrect with, %s.", - err, - ) - } - SMSSendToAll("SMSGLOBAL Test - SMSSENDTOALL", *cfg) -} +func TestGetEnabledContacts(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) -func TestSMSGetNumberByName(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) - if err != nil { - t.Errorf( - "Test Failed. SMSGetNumberByName: Function return is incorrect with, %s.", - err, - ) - } - number := SMSGetNumberByName("StyleGherkin", cfg.SMS) - if number == "" { - t.Error("Test Failed. SMSNotify Error: No number, name not found.") - } - number = SMSGetNumberByName("testy", cfg.SMS) - if number == "" { - t.Error("Test Failed. SMSNotify Error: No number, name not found.") + expected := 1 + actual := result.GetEnabledContacts() + if expected != actual { + t.Errorf("Test failed. TestGetEnabledContacts expected %d, got %d", + expected, actual) } } -func TestSMSNotify(t *testing.T) { - cfg := config.GetConfig() - err := cfg.LoadConfig(config.ConfigTestFile) +func TestGetContactByNumber(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) + + actual, err := result.GetContactByNumber(contact.Number) if err != nil { - t.Errorf( - "Test Failed. SMSNotify: \nFunction return is incorrect with, %s.", - err, - ) + t.Fatalf("Test failed. TestGetContactByNumber: %s", err) + } + + if actual.Name != contact.Name && actual.Number != contact.Number && actual.Enabled != contact.Enabled { + t.Fatal("Test failed. TestGetContactByNumber: Incorrect values") + } + + _, err = result.GetContactByNumber("ASDASDASD") + if err == nil { + t.Fatal("Test failed. TestGetContactByNumber: Returned nil err on non-existant number") } - // err2 := SMSNotify("+61312112718", "teststring", *cfg) - // if err2 != nil { - // t.Error("Test Failed. SMSNotify: \nError: ", err2) - // } +} + +func TestGetContactByName(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) + + actual, err := result.GetContactByName(contact.Name) + if err != nil { + t.Fatalf("Test failed. TestGetContactByName: %s", err) + } + + if actual.Name != contact.Name && actual.Number != contact.Number && actual.Enabled != contact.Enabled { + t.Fatal("Test failed. TestGetContactByName: Incorrect values") + } + + _, err = result.GetContactByName("ASDASDASD") + if err == nil { + t.Fatal("Test failed. TestGetContactByName: Returned nil err on non-existant number") + } +} + +func TestAddContact(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) + + // Test adding same contact + result.AddContact(contact) + if result.GetEnabledContacts() > 1 { + t.Fatal("Test failed. TestAddContact: Incorrect values") + } + + invalidContact := Contact{Name: "", Number: "", Enabled: true} + result.AddContact(invalidContact) + if result.GetEnabledContacts() > 1 { + t.Fatal("Test failed. TestAddContact: Incorrect values") + } + + newContact := Contact{Name: "newContact", Number: "12345", Enabled: true} + result.AddContact(newContact) + if result.GetEnabledContacts() != 2 { + t.Fatal("Test failed. TestAddContact: Incorrect values") + } +} + +func TestRemoveContact(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) + + result.RemoveContact(Contact{Name: "blah", Number: "1234"}) + if result.GetEnabledContacts() != 1 { + t.Fatal("Test failed. TestRemoveContact: Incorrect values") + } + + result.RemoveContact(contact) + if result.GetEnabledContacts() != 0 { + t.Fatal("Test failed. TestRemoveContact: Incorrect values") + } +} + +func TestSendMessageToAll(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) + result.SendMessageToAll("hello world") +} + +func TestSendMessage(t *testing.T) { + contact := Contact{Name: "bob", Number: "1234", Enabled: true} + var contacts []Contact + contacts = append(contacts, contact) + result := New("bob", "pw", "Skynet", contacts) + err := result.SendMessage(contact.Number, "hello world") + log.Println(err) + t.Log(err) } From 1237223b7f5d8e08fc1ef441a4bee56079c543d6 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 14 Sep 2017 15:44:45 +1000 Subject: [PATCH 24/32] Bittrex skip over empty market name --- exchanges/bittrex/bittrex_wrapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 843cc733..9aff0a89 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -32,7 +32,7 @@ func (b *Bittrex) Run() { } var currencies []string for x := range exchangeProducts { - if !exchangeProducts[x].IsActive { + if !exchangeProducts[x].IsActive || exchangeProducts[x].MarketName == "" { continue } currencies = append(currencies, exchangeProducts[x].MarketName) From 9114de8a06371d76f72908a4909ff77b862f92fa Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Thu, 14 Sep 2017 15:51:25 +1000 Subject: [PATCH 25/32] Liqui skip empty pairs --- exchanges/liqui/liqui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index ca9c5f6a..4611148e 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -91,7 +91,7 @@ func (l *Liqui) GetFee(currency string) (float64, error) { func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { var pairs []string for x, y := range l.Info.Pairs { - if nonHidden && y.Hidden == 1 { + if nonHidden && y.Hidden == 1 || x == "" { continue } pairs = append(pairs, common.StringToUpper(x)) From 542828e9570434a985ee20a9f89570bbe7e41747 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Sat, 16 Sep 2017 13:03:00 +1000 Subject: [PATCH 26/32] Rename BTC-e to WEX and reinstate exchange --- README.md | 2 +- config_example.dat | 44 +- exchanges/btce/btce.go | 363 ---------------- exchanges/btce/btce_wrapper.go | 135 ------ exchanges/wex/wex.go | 393 ++++++++++++++++++ .../{btce/btce_types.go => wex/wex_types.go} | 42 +- exchanges/wex/wex_wrapper.go | 114 +++++ main.go | 6 +- 8 files changed, 556 insertions(+), 543 deletions(-) delete mode 100644 exchanges/btce/btce.go delete mode 100644 exchanges/btce/btce_wrapper.go create mode 100644 exchanges/wex/wex.go rename exchanges/{btce/btce_types.go => wex/wex_types.go} (83%) create mode 100644 exchanges/wex/wex_wrapper.go diff --git a/README.md b/README.md index 9841947f..4c48fe1f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Bitstamp | Yes | Yes | NA | | Bittrex | Yes | No | NA | | BTCC | Yes | Yes | No | -| BTCE | Yes | NA | NA | | BTCMarkets | Yes | NA | NA | | COINUT | Yes | No | NA | | GDAX(Coinbase) | Yes | Yes | No| @@ -37,6 +36,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | LocalBitcoins | Yes | NA | NA | | OKCoin (both) | Yes | Yes | No | | Poloniex | Yes | Yes | NA | +| WEX | Yes | NA | NA | We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/). diff --git a/config_example.dat b/config_example.dat index ff0a64e2..baad385a 100644 --- a/config_example.dat +++ b/config_example.dat @@ -158,28 +158,6 @@ "Uppercase": false } }, - { - "Name": "BTCE", - "Enabled": false, - "Verbose": false, - "Websocket": false, - "RESTPollingDelay": 10, - "AuthenticatedAPISupport": false, - "APIKey": "Key", - "APISecret": "Secret", - "AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", - "EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", - "BaseCurrencies": "USD,RUR,EUR", - "AssetTypes": "SPOT", - "ConfigCurrencyPairFormat": { - "Uppercase": true - }, - "RequestCurrencyPairFormat": { - "Uppercase": false, - "Delimiter": "_", - "Separator": "-" - } - }, { "Name": "BTC Markets", "Enabled": true, @@ -451,6 +429,28 @@ "Uppercase": true, "Delimiter": "_" } + }, + { + "Name": "WEX", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", + "EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD", + "BaseCurrencies": "USD,RUR,EUR", + "AssetTypes": "SPOT", + "ConfigCurrencyPairFormat": { + "Uppercase": true + }, + "RequestCurrencyPairFormat": { + "Uppercase": false, + "Delimiter": "_", + "Separator": "-" + } } ] } \ No newline at end of file diff --git a/exchanges/btce/btce.go b/exchanges/btce/btce.go deleted file mode 100644 index b251a4b5..00000000 --- a/exchanges/btce/btce.go +++ /dev/null @@ -1,363 +0,0 @@ -package btce - -import ( - "errors" - "fmt" - "log" - "net/url" - "strconv" - "strings" - "time" - - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/config" - "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" -) - -const ( - BTCE_API_PUBLIC_URL = "https://btc-e.com/api" - BTCE_API_PRIVATE_URL = "https://btc-e.com/tapi" - BTCE_API_PUBLIC_VERSION = "3" - BTCE_API_PRIVATE_VERSION = "1" - BTCE_INFO = "info" - BTCE_TICKER = "ticker" - BTCE_DEPTH = "depth" - BTCE_TRADES = "trades" - BTCE_ACCOUNT_INFO = "getInfo" - BTCE_TRADE = "Trade" - BTCE_ACTIVE_ORDERS = "ActiveOrders" - BTCE_ORDER_INFO = "OrderInfo" - BTCE_CANCEL_ORDER = "CancelOrder" - BTCE_TRADE_HISTORY = "TradeHistory" - BTCE_TRANSACTION_HISTORY = "TransHistory" - BTCE_WITHDRAW_COIN = "WithdrawCoin" - BTCE_CREATE_COUPON = "CreateCoupon" - BTCE_REDEEM_COUPON = "RedeemCoupon" -) - -type BTCE struct { - exchange.Base - Ticker map[string]BTCeTicker -} - -func (b *BTCE) SetDefaults() { - b.Name = "BTCE" - b.Enabled = false - b.Fee = 0.2 - b.Verbose = false - b.Websocket = false - b.RESTPollingDelay = 10 - b.Ticker = make(map[string]BTCeTicker) - b.RequestCurrencyPairFormat.Delimiter = "_" - b.RequestCurrencyPairFormat.Uppercase = false - b.RequestCurrencyPairFormat.Separator = "-" - b.ConfigCurrencyPairFormat.Delimiter = "" - b.ConfigCurrencyPairFormat.Uppercase = true - b.AssetTypes = []string{ticker.Spot} -} - -func (b *BTCE) Setup(exch config.ExchangeConfig) { - if !exch.Enabled { - b.SetEnabled(false) - } else { - b.Enabled = true - b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport - b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) - b.RESTPollingDelay = exch.RESTPollingDelay - b.Verbose = exch.Verbose - b.Websocket = exch.Websocket - b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") - b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") - b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") - err := b.SetCurrencyPairFormat() - if err != nil { - log.Fatal(err) - } - err = b.SetAssetTypes() - if err != nil { - log.Fatal(err) - } - } -} - -func (b *BTCE) GetFee() float64 { - return b.Fee -} - -func (b *BTCE) GetInfo() (BTCEInfo, error) { - req := fmt.Sprintf("%s/%s/%s/", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_INFO) - resp := BTCEInfo{} - err := common.SendHTTPGetRequest(req, true, &resp) - - if err != nil { - return resp, err - } - - return resp, nil -} - -func (b *BTCE) GetTicker(symbol string) (map[string]BTCeTicker, error) { - type Response struct { - Data map[string]BTCeTicker - } - - response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_TICKER, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) - - if err != nil { - return nil, err - } - return response.Data, nil -} - -func (b *BTCE) GetDepth(symbol string) (BTCEOrderbook, error) { - type Response struct { - Data map[string]BTCEOrderbook - } - - response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_DEPTH, symbol) - - err := common.SendHTTPGetRequest(req, true, &response.Data) - if err != nil { - return BTCEOrderbook{}, err - } - - depth := response.Data[symbol] - return depth, nil -} - -func (b *BTCE) GetTrades(symbol string) ([]BTCETrades, error) { - type Response struct { - Data map[string][]BTCETrades - } - - response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_TRADES, symbol) - - err := common.SendHTTPGetRequest(req, true, &response.Data) - if err != nil { - return []BTCETrades{}, err - } - - trades := response.Data[symbol] - return trades, nil -} - -func (b *BTCE) GetAccountInfo() (BTCEAccountInfo, error) { - var result BTCEAccountInfo - err := b.SendAuthenticatedHTTPRequest(BTCE_ACCOUNT_INFO, url.Values{}, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) GetActiveOrders(pair string) (map[string]BTCEActiveOrders, error) { - req := url.Values{} - req.Add("pair", pair) - - var result map[string]BTCEActiveOrders - err := b.SendAuthenticatedHTTPRequest(BTCE_ACTIVE_ORDERS, req, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) GetOrderInfo(OrderID int64) (map[string]BTCEOrderInfo, error) { - req := url.Values{} - req.Add("order_id", strconv.FormatInt(OrderID, 10)) - - var result map[string]BTCEOrderInfo - err := b.SendAuthenticatedHTTPRequest(BTCE_ORDER_INFO, req, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) CancelOrder(OrderID int64) (bool, error) { - req := url.Values{} - req.Add("order_id", strconv.FormatInt(OrderID, 10)) - - var result BTCECancelOrder - err := b.SendAuthenticatedHTTPRequest(BTCE_CANCEL_ORDER, req, &result) - - if err != nil { - return false, err - } - - return true, nil -} - -//to-do: convert orderid to int64 -func (b *BTCE) Trade(pair, orderType string, amount, price float64) (float64, error) { - req := url.Values{} - req.Add("pair", pair) - req.Add("type", orderType) - req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64)) - - var result BTCETrade - err := b.SendAuthenticatedHTTPRequest(BTCE_TRADE, req, &result) - - if err != nil { - return 0, err - } - - return result.OrderID, nil -} - -func (b *BTCE) GetTransactionHistory(TIDFrom, Count, TIDEnd int64, order, since, end string) (map[string]BTCETransHistory, error) { - req := url.Values{} - req.Add("from", strconv.FormatInt(TIDFrom, 10)) - req.Add("count", strconv.FormatInt(Count, 10)) - req.Add("from_id", strconv.FormatInt(TIDFrom, 10)) - req.Add("end_id", strconv.FormatInt(TIDEnd, 10)) - req.Add("order", order) - req.Add("since", since) - req.Add("end", end) - - var result map[string]BTCETransHistory - err := b.SendAuthenticatedHTTPRequest(BTCE_TRANSACTION_HISTORY, req, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, pair string) (map[string]BTCETradeHistory, error) { - req := url.Values{} - - req.Add("from", strconv.FormatInt(TIDFrom, 10)) - req.Add("count", strconv.FormatInt(Count, 10)) - req.Add("from_id", strconv.FormatInt(TIDFrom, 10)) - req.Add("end_id", strconv.FormatInt(TIDEnd, 10)) - req.Add("order", order) - req.Add("since", since) - req.Add("end", end) - req.Add("pair", pair) - - var result map[string]BTCETradeHistory - err := b.SendAuthenticatedHTTPRequest(BTCE_TRADE_HISTORY, req, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) WithdrawCoins(coin string, amount float64, address string) (BTCEWithdrawCoins, error) { - req := url.Values{} - - req.Add("coinName", coin) - req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - req.Add("address", address) - - var result BTCEWithdrawCoins - err := b.SendAuthenticatedHTTPRequest(BTCE_WITHDRAW_COIN, req, &result) - - if err != nil { - return result, err - } - return result, nil -} - -func (b *BTCE) CreateCoupon(currency string, amount float64) (BTCECreateCoupon, error) { - req := url.Values{} - - req.Add("currency", currency) - req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - - var result BTCECreateCoupon - err := b.SendAuthenticatedHTTPRequest(BTCE_CREATE_COUPON, req, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) RedeemCoupon(coupon string) (BTCERedeemCoupon, error) { - req := url.Values{} - - req.Add("coupon", coupon) - - var result BTCERedeemCoupon - err := b.SendAuthenticatedHTTPRequest(BTCE_REDEEM_COUPON, req, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (b *BTCE) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { - if !b.AuthenticatedAPISupport { - return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) - } - - if b.Nonce.Get() == 0 { - b.Nonce.Set(time.Now().Unix()) - } else { - b.Nonce.Inc() - } - values.Set("nonce", b.Nonce.String()) - values.Set("method", method) - - encoded := values.Encode() - hmac := common.GetHMAC(common.HashSHA512, []byte(encoded), []byte(b.APISecret)) - - if b.Verbose { - log.Printf("Sending POST request to %s calling method %s with params %s\n", BTCE_API_PRIVATE_URL, method, encoded) - } - - headers := make(map[string]string) - headers["Key"] = b.APIKey - headers["Sign"] = common.HexEncodeToString(hmac) - headers["Content-Type"] = "application/x-www-form-urlencoded" - - resp, err := common.SendHTTPRequest("POST", BTCE_API_PRIVATE_URL, headers, strings.NewReader(encoded)) - - if err != nil { - return err - } - - response := BTCEResponse{} - err = common.JSONDecode([]byte(resp), &response) - - if err != nil { - return err - } - - if response.Success != 1 { - return errors.New(response.Error) - } - - JSONEncoded, err := common.JSONEncode(response.Return) - - if err != nil { - return err - } - - err = common.JSONDecode(JSONEncoded, &result) - - if err != nil { - return err - } - return nil -} diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go deleted file mode 100644 index 282f9a38..00000000 --- a/exchanges/btce/btce_wrapper.go +++ /dev/null @@ -1,135 +0,0 @@ -package btce - -import ( - "errors" - "log" - "time" - - "github.com/thrasher-/gocryptotrader/common" - "github.com/thrasher-/gocryptotrader/currency/pair" - "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" -) - -// Start starts the BTCE go routine -func (b *BTCE) Start() { - go b.Run() -} - -// Run implements the BTCE wrapper -func (b *BTCE) Run() { - if b.Verbose { - log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) - log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) - log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) - } - - pairs := b.GetEnabledCurrencies() - pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(b.Name, pairs) - if err != nil { - log.Println(err) - b.Enabled = false - return - } - - for b.Enabled { - go func() { - ticker, err := b.GetTicker(pairsCollated.String()) - if err != nil { - log.Println(err) - return - } - for x, y := range ticker { - x = common.StringToUpper(x[0:3] + x[4:]) - log.Printf("BTC-e %s: Last %f High %f Low %f Volume %f\n", x, y.Last, y.High, y.Low, y.Vol_cur) - b.Ticker[x] = y - } - }() - time.Sleep(time.Second * b.RESTPollingDelay) - } -} - -// UpdateTicker updates and returns the ticker for a currency pair -func (b *BTCE) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { - var tickerPrice ticker.Price - result, err := b.GetTicker(p.Pair().String()) - if err != nil { - return tickerPrice, err - } - - tick, ok := result[p.Pair().Lower().String()] - if !ok { - return tickerPrice, errors.New("unable to get currency") - } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Buy - tickerPrice.Bid = tick.Sell - tickerPrice.Low = tick.Low - tickerPrice.Last = tick.Last - tickerPrice.Volume = tick.Vol_cur - tickerPrice.High = tick.High - ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType) - return ticker.GetTicker(b.Name, p, assetType) -} - -// GetTickerPrice returns the ticker for a currency pair -func (b *BTCE) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { - tick, err := ticker.GetTicker(b.GetName(), p, assetType) - if err != nil { - return b.UpdateTicker(p, assetType) - } - return tick, nil -} - -// GetOrderbookEx returns the orderbook for a currency pair -func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { - ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType) - if err == nil { - return b.UpdateOrderbook(p, assetType) - } - return ob, nil -} - -// UpdateOrderbook updates and returns the orderbook for a currency pair -func (b *BTCE) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { - var orderBook orderbook.Base - orderbookNew, err := b.GetDepth(exchange.FormatExchangeCurrency(b.Name, p).String()) - if err != nil { - return orderBook, err - } - - for x := range orderbookNew.Bids { - data := orderbookNew.Bids[x] - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: data[0], Amount: data[1]}) - } - - for x := range orderbookNew.Asks { - data := orderbookNew.Asks[x] - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]}) - } - - orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType) - return orderbook.GetOrderbook(b.Name, p, assetType) -} - -// GetExchangeAccountInfo retrieves balances for all enabled currencies for the -// BTCE exchange -func (b *BTCE) GetExchangeAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo - response.ExchangeName = b.GetName() - accountBalance, err := b.GetAccountInfo() - if err != nil { - return response, err - } - - for x, y := range accountBalance.Funds { - var exchangeCurrency exchange.AccountCurrencyInfo - exchangeCurrency.CurrencyName = common.StringToUpper(x) - exchangeCurrency.TotalValue = y - exchangeCurrency.Hold = 0 - response.Currencies = append(response.Currencies, exchangeCurrency) - } - - return response, nil -} diff --git a/exchanges/wex/wex.go b/exchanges/wex/wex.go new file mode 100644 index 00000000..3ac246c1 --- /dev/null +++ b/exchanges/wex/wex.go @@ -0,0 +1,393 @@ +package wex + +import ( + "errors" + "fmt" + "log" + "net/url" + "strconv" + "strings" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +const ( + wexAPIPublicURL = "https://wex.nz/api" + wexAPIPrivateURL = "https://wex.nz/tapi" + wexAPIPublicVersion = "3" + wexAPIPrivateVersion = "1" + wexInfo = "info" + wexTicker = "ticker" + wexDepth = "depth" + wexTrades = "trades" + wexAccountInfo = "getInfo" + wexTrade = "Trade" + wexActiveOrders = "ActiveOrders" + wexOrderInfo = "OrderInfo" + wexCancelOrder = "CancelOrder" + wexTradeHistory = "TradeHistory" + wexTransactionHistory = "TransHistory" + wexWithdrawCoin = "WithdrawCoin" + wexCoinDepositAddress = "CoinDepositAddress" + wexCreateCoupon = "CreateCoupon" + wexRedeemCoupon = "RedeemCoupon" +) + +// WEX is the overarching type across the wex package +type WEX struct { + exchange.Base + Ticker map[string]Ticker +} + +// SetDefaults sets current default value for WEX +func (w *WEX) SetDefaults() { + w.Name = "WEX" + w.Enabled = false + w.Fee = 0.2 + w.Verbose = false + w.Websocket = false + w.RESTPollingDelay = 10 + w.Ticker = make(map[string]Ticker) + w.RequestCurrencyPairFormat.Delimiter = "_" + w.RequestCurrencyPairFormat.Uppercase = false + w.RequestCurrencyPairFormat.Separator = "-" + w.ConfigCurrencyPairFormat.Delimiter = "" + w.ConfigCurrencyPairFormat.Uppercase = true + w.AssetTypes = []string{ticker.Spot} +} + +// Setup sets exchange configuration parameters for WEX +func (w *WEX) Setup(exch config.ExchangeConfig) { + if !exch.Enabled { + w.SetEnabled(false) + } else { + w.Enabled = true + w.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + w.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) + w.RESTPollingDelay = exch.RESTPollingDelay + w.Verbose = exch.Verbose + w.Websocket = exch.Websocket + w.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") + w.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") + w.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + err := w.SetCurrencyPairFormat() + if err != nil { + log.Fatal(err) + } + err = w.SetAssetTypes() + if err != nil { + log.Fatal(err) + } + } +} + +// GetFee returns the exchange fee +func (w *WEX) GetFee() float64 { + return w.Fee +} + +// GetInfo returns the WEX info +func (w *WEX) GetInfo() (Info, error) { + req := fmt.Sprintf("%s/%s/%s/", wexAPIPublicURL, wexAPIPublicVersion, wexInfo) + resp := Info{} + err := common.SendHTTPGetRequest(req, true, &resp) + + if err != nil { + return resp, err + } + + return resp, nil +} + +// GetTicker returns a ticker for a specific currency +func (w *WEX) GetTicker(symbol string) (map[string]Ticker, error) { + type Response struct { + Data map[string]Ticker + } + + response := Response{} + req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTicker, symbol) + err := common.SendHTTPGetRequest(req, true, &response.Data) + + if err != nil { + return nil, err + } + return response.Data, nil +} + +// GetDepth returns the depth for a specific currency +func (w *WEX) GetDepth(symbol string) (Orderbook, error) { + type Response struct { + Data map[string]Orderbook + } + + response := Response{} + req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexDepth, symbol) + + err := common.SendHTTPGetRequest(req, true, &response.Data) + if err != nil { + return Orderbook{}, err + } + + depth := response.Data[symbol] + return depth, nil +} + +// GetTrades returns the trades for a specific currency +func (w *WEX) GetTrades(symbol string) ([]Trades, error) { + type Response struct { + Data map[string][]Trades + } + + response := Response{} + req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTrades, symbol) + + err := common.SendHTTPGetRequest(req, true, &response.Data) + if err != nil { + return nil, err + } + + trades := response.Data[symbol] + return trades, nil +} + +// GetAccountInfo returns a users account info +func (w *WEX) GetAccountInfo() (AccountInfo, error) { + var result AccountInfo + err := w.SendAuthenticatedHTTPRequest(wexAccountInfo, url.Values{}, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// GetActiveOrders returns the active orders for a specific currency +func (w *WEX) GetActiveOrders(pair string) (map[string]ActiveOrders, error) { + req := url.Values{} + req.Add("pair", pair) + + var result map[string]ActiveOrders + err := w.SendAuthenticatedHTTPRequest(wexActiveOrders, req, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// GetOrderInfo returns the order info for a specific order ID +func (w *WEX) GetOrderInfo(OrderID int64) (map[string]OrderInfo, error) { + req := url.Values{} + req.Add("order_id", strconv.FormatInt(OrderID, 10)) + + var result map[string]OrderInfo + err := w.SendAuthenticatedHTTPRequest(wexOrderInfo, req, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// CancelOrder cancels an order for a specific order ID +func (w *WEX) CancelOrder(OrderID int64) (bool, error) { + req := url.Values{} + req.Add("order_id", strconv.FormatInt(OrderID, 10)) + + var result CancelOrder + err := w.SendAuthenticatedHTTPRequest(wexCancelOrder, req, &result) + + if err != nil { + return false, err + } + + return true, nil +} + +// Trade places an order and returns the order ID if successful or an error +func (w *WEX) Trade(pair, orderType string, amount, price float64) (int64, error) { + req := url.Values{} + req.Add("pair", pair) + req.Add("type", orderType) + req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64)) + + var result Trade + err := w.SendAuthenticatedHTTPRequest(wexTrade, req, &result) + + if err != nil { + return 0, err + } + + return int64(result.OrderID), nil +} + +// GetTransactionHistory returns the transaction history +func (w *WEX) GetTransactionHistory(TIDFrom, Count, TIDEnd int64, order, since, end string) (map[string]TransHistory, error) { + req := url.Values{} + req.Add("from", strconv.FormatInt(TIDFrom, 10)) + req.Add("count", strconv.FormatInt(Count, 10)) + req.Add("from_id", strconv.FormatInt(TIDFrom, 10)) + req.Add("end_id", strconv.FormatInt(TIDEnd, 10)) + req.Add("order", order) + req.Add("since", since) + req.Add("end", end) + + var result map[string]TransHistory + err := w.SendAuthenticatedHTTPRequest(wexTransactionHistory, req, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// GetTradeHistory returns the trade history +func (w *WEX) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, pair string) (map[string]TradeHistory, error) { + req := url.Values{} + req.Add("from", strconv.FormatInt(TIDFrom, 10)) + req.Add("count", strconv.FormatInt(Count, 10)) + req.Add("from_id", strconv.FormatInt(TIDFrom, 10)) + req.Add("end_id", strconv.FormatInt(TIDEnd, 10)) + req.Add("order", order) + req.Add("since", since) + req.Add("end", end) + req.Add("pair", pair) + + var result map[string]TradeHistory + err := w.SendAuthenticatedHTTPRequest(wexTradeHistory, req, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// WithdrawCoins withdraws coins for a specific coin +func (w *WEX) WithdrawCoins(coin string, amount float64, address string) (WithdrawCoins, error) { + req := url.Values{} + req.Add("coinName", coin) + req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + req.Add("address", address) + + var result WithdrawCoins + err := w.SendAuthenticatedHTTPRequest(wexWithdrawCoin, req, &result) + + if err != nil { + return result, err + } + return result, nil +} + +// CoinDepositAddress returns the deposit address for a specific currency +func (w *WEX) CoinDepositAddress(coin string) (string, error) { + req := url.Values{} + req.Add("coinName", coin) + + var result CoinDepositAddress + err := w.SendAuthenticatedHTTPRequest(wexCoinDepositAddress, req, &result) + + if err != nil { + return "", nil + } + + return result.Address, nil +} + +// CreateCoupon creates an exchange coupon for a sepcific currency +func (w *WEX) CreateCoupon(currency string, amount float64) (CreateCoupon, error) { + req := url.Values{} + req.Add("currency", currency) + req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + + var result CreateCoupon + err := w.SendAuthenticatedHTTPRequest(wexCreateCoupon, req, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// RedeemCoupon redeems an exchange coupon +func (w *WEX) RedeemCoupon(coupon string) (RedeemCoupon, error) { + req := url.Values{} + req.Add("coupon", coupon) + + var result RedeemCoupon + err := w.SendAuthenticatedHTTPRequest(wexRedeemCoupon, req, &result) + + if err != nil { + return result, err + } + + return result, nil +} + +// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to WEX +func (w *WEX) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { + if !w.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, w.Name) + } + + if w.Nonce.Get() == 0 { + w.Nonce.Set(time.Now().Unix()) + } else { + w.Nonce.Inc() + } + values.Set("nonce", w.Nonce.String()) + values.Set("method", method) + + encoded := values.Encode() + hmac := common.GetHMAC(common.HashSHA512, []byte(encoded), []byte(w.APISecret)) + + if w.Verbose { + log.Printf("Sending POST request to %s calling method %s with params %s\n", wexAPIPrivateURL, method, encoded) + } + + headers := make(map[string]string) + headers["Key"] = w.APIKey + headers["Sign"] = common.HexEncodeToString(hmac) + headers["Content-Type"] = "application/x-www-form-urlencoded" + + resp, err := common.SendHTTPRequest("POST", wexAPIPrivateURL, headers, strings.NewReader(encoded)) + + if err != nil { + return err + } + + response := Response{} + err = common.JSONDecode([]byte(resp), &response) + + if err != nil { + return err + } + + if response.Success != 1 { + return errors.New(response.Error) + } + + JSONEncoded, err := common.JSONEncode(response.Return) + + if err != nil { + return err + } + + err = common.JSONDecode(JSONEncoded, &result) + + if err != nil { + return err + } + return nil +} diff --git a/exchanges/btce/btce_types.go b/exchanges/wex/wex_types.go similarity index 83% rename from exchanges/btce/btce_types.go rename to exchanges/wex/wex_types.go index a1f8b8e5..5f365166 100644 --- a/exchanges/btce/btce_types.go +++ b/exchanges/wex/wex_types.go @@ -1,6 +1,6 @@ -package btce +package wex -type BTCeTicker struct { +type Ticker struct { High float64 Low float64 Avg float64 @@ -12,12 +12,12 @@ type BTCeTicker struct { Updated int64 } -type BTCEOrderbook struct { +type Orderbook struct { Asks [][]float64 `json:"asks"` Bids [][]float64 `json:"bids"` } -type BTCETrades struct { +type Trades struct { Type string `json:"type"` Price float64 `json:"bid"` Amount float64 `json:"amount"` @@ -25,13 +25,13 @@ type BTCETrades struct { Timestamp int64 `json:"timestamp"` } -type BTCEResponse struct { +type Response struct { Return interface{} `json:"return"` Success int `json:"success"` Error string `json:"error"` } -type BTCEPair struct { +type Pair struct { DecimalPlaces int `json:"decimal_places"` MinPrice float64 `json:"min_price"` MaxPrice float64 `json:"max_price"` @@ -40,12 +40,12 @@ type BTCEPair struct { Fee float64 `json:"fee"` } -type BTCEInfo struct { - ServerTime int64 `json:"server_time"` - Pairs map[string]BTCEPair `json:"pairs"` +type Info struct { + ServerTime int64 `json:"server_time"` + Pairs map[string]Pair `json:"pairs"` } -type BTCEAccountInfo struct { +type AccountInfo struct { Funds map[string]float64 `json:"funds"` OpenOrders int `json:"open_orders"` Rights struct { @@ -57,7 +57,7 @@ type BTCEAccountInfo struct { TransactionCount int `json:"transaction_count"` } -type BTCEActiveOrders struct { +type ActiveOrders struct { Pair string `json:"pair"` Type string `json:"sell"` Amount float64 `json:"amount"` @@ -66,7 +66,7 @@ type BTCEActiveOrders struct { Status int `json:"status"` } -type BTCEOrderInfo struct { +type OrderInfo struct { Pair string `json:"pair"` Type string `json:"sell"` StartAmount float64 `json:"start_amount"` @@ -76,19 +76,19 @@ type BTCEOrderInfo struct { Status int `json:"status"` } -type BTCECancelOrder struct { +type CancelOrder struct { OrderID float64 `json:"order_id"` Funds map[string]float64 `json:"funds"` } -type BTCETrade struct { +type Trade struct { Received float64 `json:"received"` Remains float64 `json:"remains"` OrderID float64 `json:"order_id"` Funds map[string]float64 `json:"funds"` } -type BTCETransHistory struct { +type TransHistory struct { Type int `json:"type"` Amount float64 `json:"amount"` Currency string `json:"currency"` @@ -97,7 +97,7 @@ type BTCETransHistory struct { Timestamp float64 `json:"timestamp"` } -type BTCETradeHistory struct { +type TradeHistory struct { Pair string `json:"pair"` Type string `json:"type"` Amount float64 `json:"amount"` @@ -107,19 +107,23 @@ type BTCETradeHistory struct { Timestamp float64 `json:"timestamp"` } -type BTCEWithdrawCoins struct { +type CoinDepositAddress struct { + Address string `json:"address"` +} + +type WithdrawCoins struct { TID int64 `json:"tId"` AmountSent float64 `json:"amountSent"` Funds map[string]float64 `json:"funds"` } -type BTCECreateCoupon struct { +type CreateCoupon struct { Coupon string `json:"coupon"` TransID int64 `json:"transID"` Funds map[string]float64 `json:"funds"` } -type BTCERedeemCoupon struct { +type RedeemCoupon struct { CouponAmount float64 `json:"couponAmount,string"` CouponCurrency string `json:"couponCurrency"` TransID int64 `json:"transID"` diff --git a/exchanges/wex/wex_wrapper.go b/exchanges/wex/wex_wrapper.go new file mode 100644 index 00000000..d2d3a884 --- /dev/null +++ b/exchanges/wex/wex_wrapper.go @@ -0,0 +1,114 @@ +package wex + +import ( + "log" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + exchange "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// Start starts the BTCE go routine +func (w *WEX) Start() { + go w.Run() +} + +// Run implements the BTCE wrapper +func (w *WEX) Run() { + if w.Verbose { + log.Printf("%s Websocket: %s.", w.GetName(), common.IsEnabled(w.Websocket)) + log.Printf("%s polling delay: %ds.\n", w.GetName(), w.RESTPollingDelay) + log.Printf("%s %d currencies enabled: %s.\n", w.GetName(), len(w.EnabledPairs), w.EnabledPairs) + } +} + +// UpdateTicker updates and returns the ticker for a currency pair +func (w *WEX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + var tickerPrice ticker.Price + pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(w.Name, w.GetEnabledCurrencies()) + if err != nil { + return tickerPrice, err + } + + result, err := w.GetTicker(pairsCollated.String()) + if err != nil { + return tickerPrice, err + } + + for _, x := range w.GetEnabledCurrencies() { + currency := exchange.FormatExchangeCurrency(w.Name, x).Lower().String() + var tp ticker.Price + tp.Pair = x + tp.Last = result[currency].Last + tp.Ask = result[currency].Sell + tp.Bid = result[currency].Buy + tp.Last = result[currency].Last + tp.Low = result[currency].Low + tp.Volume = result[currency].Vol_cur + ticker.ProcessTicker(w.Name, x, tp, assetType) + } + return ticker.GetTicker(w.Name, p, assetType) +} + +// GetTickerPrice returns the ticker for a currency pair +func (w *WEX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { + tick, err := ticker.GetTicker(w.GetName(), p, assetType) + if err != nil { + return w.UpdateTicker(p, assetType) + } + return tick, nil +} + +// GetOrderbookEx returns the orderbook for a currency pair +func (w *WEX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + ob, err := orderbook.GetOrderbook(w.GetName(), p, assetType) + if err == nil { + return w.UpdateOrderbook(p, assetType) + } + return ob, nil +} + +// UpdateOrderbook updates and returns the orderbook for a currency pair +func (w *WEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { + var orderBook orderbook.Base + orderbookNew, err := w.GetDepth(exchange.FormatExchangeCurrency(w.Name, p).String()) + if err != nil { + return orderBook, err + } + + for x := range orderbookNew.Bids { + data := orderbookNew.Bids[x] + orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: data[0], Amount: data[1]}) + } + + for x := range orderbookNew.Asks { + data := orderbookNew.Asks[x] + orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]}) + } + + orderbook.ProcessOrderbook(w.GetName(), p, orderBook, assetType) + return orderbook.GetOrderbook(w.Name, p, assetType) +} + +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// BTCE exchange +func (w *WEX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { + var response exchange.AccountInfo + response.ExchangeName = w.GetName() + accountBalance, err := w.GetAccountInfo() + if err != nil { + return response, err + } + + for x, y := range accountBalance.Funds { + var exchangeCurrency exchange.AccountCurrencyInfo + exchangeCurrency.CurrencyName = common.StringToUpper(x) + exchangeCurrency.TotalValue = y + exchangeCurrency.Hold = 0 + response.Currencies = append(response.Currencies, exchangeCurrency) + } + + return response, nil +} diff --git a/main.go b/main.go index 967b20af..7d6dc1a5 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,6 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" "github.com/thrasher-/gocryptotrader/exchanges/bittrex" "github.com/thrasher-/gocryptotrader/exchanges/btcc" - "github.com/thrasher-/gocryptotrader/exchanges/btce" "github.com/thrasher-/gocryptotrader/exchanges/btcmarkets" "github.com/thrasher-/gocryptotrader/exchanges/coinut" "github.com/thrasher-/gocryptotrader/exchanges/gdax" @@ -33,6 +32,7 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/okcoin" "github.com/thrasher-/gocryptotrader/exchanges/poloniex" "github.com/thrasher-/gocryptotrader/exchanges/ticker" + "github.com/thrasher-/gocryptotrader/exchanges/wex" "github.com/thrasher-/gocryptotrader/portfolio" "github.com/thrasher-/gocryptotrader/smsglobal" ) @@ -44,7 +44,7 @@ type ExchangeMain struct { bitstamp bitstamp.Bitstamp bitfinex bitfinex.Bitfinex bittrex bittrex.Bittrex - btce btce.BTCE + wex wex.WEX btcmarkets btcmarkets.BTCMarkets coinut coinut.COINUT gdax gdax.GDAX @@ -144,7 +144,7 @@ func main() { new(bitstamp.Bitstamp), new(bitfinex.Bitfinex), new(bittrex.Bittrex), - new(btce.BTCE), + new(wex.WEX), new(btcmarkets.BTCMarkets), new(coinut.COINUT), new(gdax.GDAX), From 05b2bc0d561674dc4e9deb02fc43143840a92b84 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Sat, 16 Sep 2017 13:18:28 +1000 Subject: [PATCH 27/32] Fix WEX linter issues --- exchanges/wex/wex_types.go | 35 ++++++++++++++++++++++++++--------- exchanges/wex/wex_wrapper.go | 8 ++++---- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/exchanges/wex/wex_types.go b/exchanges/wex/wex_types.go index 5f365166..a83b654e 100644 --- a/exchanges/wex/wex_types.go +++ b/exchanges/wex/wex_types.go @@ -1,22 +1,25 @@ package wex +// Ticker stores the ticker information type Ticker struct { - High float64 - Low float64 - Avg float64 - Vol float64 - Vol_cur float64 - Last float64 - Buy float64 - Sell float64 - Updated int64 + High float64 + Low float64 + Avg float64 + Vol float64 + VolumeCurrent float64 `json:"vol_cur"` + Last float64 + Buy float64 + Sell float64 + Updated int64 } +// Orderbook stores the asks and bids orderbook information type Orderbook struct { Asks [][]float64 `json:"asks"` Bids [][]float64 `json:"bids"` } +// Trades stores trade information type Trades struct { Type string `json:"type"` Price float64 `json:"bid"` @@ -25,12 +28,14 @@ type Trades struct { Timestamp int64 `json:"timestamp"` } +// Response is a generic struct used for exchange API request result type Response struct { Return interface{} `json:"return"` Success int `json:"success"` Error string `json:"error"` } +// Pair holds pair information type Pair struct { DecimalPlaces int `json:"decimal_places"` MinPrice float64 `json:"min_price"` @@ -40,11 +45,13 @@ type Pair struct { Fee float64 `json:"fee"` } +// Info holds server time and pair information type Info struct { ServerTime int64 `json:"server_time"` Pairs map[string]Pair `json:"pairs"` } +// AccountInfo stores the account information for a user type AccountInfo struct { Funds map[string]float64 `json:"funds"` OpenOrders int `json:"open_orders"` @@ -57,6 +64,7 @@ type AccountInfo struct { TransactionCount int `json:"transaction_count"` } +// ActiveOrders stores active order information type ActiveOrders struct { Pair string `json:"pair"` Type string `json:"sell"` @@ -66,6 +74,7 @@ type ActiveOrders struct { Status int `json:"status"` } +// OrderInfo stores order information type OrderInfo struct { Pair string `json:"pair"` Type string `json:"sell"` @@ -76,11 +85,13 @@ type OrderInfo struct { Status int `json:"status"` } +// CancelOrder is used for the CancelOrder API request response type CancelOrder struct { OrderID float64 `json:"order_id"` Funds map[string]float64 `json:"funds"` } +// Trade stores the trade information type Trade struct { Received float64 `json:"received"` Remains float64 `json:"remains"` @@ -88,6 +99,7 @@ type Trade struct { Funds map[string]float64 `json:"funds"` } +// TransHistory stores transaction history type TransHistory struct { Type int `json:"type"` Amount float64 `json:"amount"` @@ -97,6 +109,7 @@ type TransHistory struct { Timestamp float64 `json:"timestamp"` } +// TradeHistory stores trade history type TradeHistory struct { Pair string `json:"pair"` Type string `json:"type"` @@ -107,22 +120,26 @@ type TradeHistory struct { Timestamp float64 `json:"timestamp"` } +// CoinDepositAddress stores a curency deposit address type CoinDepositAddress struct { Address string `json:"address"` } +// WithdrawCoins stores information for a withdrawcoins request type WithdrawCoins struct { TID int64 `json:"tId"` AmountSent float64 `json:"amountSent"` Funds map[string]float64 `json:"funds"` } +// CreateCoupon stores information coupon information type CreateCoupon struct { Coupon string `json:"coupon"` TransID int64 `json:"transID"` Funds map[string]float64 `json:"funds"` } +// RedeemCoupon stores redeem coupon information type RedeemCoupon struct { CouponAmount float64 `json:"couponAmount,string"` CouponCurrency string `json:"couponCurrency"` diff --git a/exchanges/wex/wex_wrapper.go b/exchanges/wex/wex_wrapper.go index d2d3a884..67b832e8 100644 --- a/exchanges/wex/wex_wrapper.go +++ b/exchanges/wex/wex_wrapper.go @@ -10,12 +10,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -// Start starts the BTCE go routine +// Start starts the WEX go routine func (w *WEX) Start() { go w.Run() } -// Run implements the BTCE wrapper +// Run implements the WEX wrapper func (w *WEX) Run() { if w.Verbose { log.Printf("%s Websocket: %s.", w.GetName(), common.IsEnabled(w.Websocket)) @@ -46,7 +46,7 @@ func (w *WEX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, tp.Bid = result[currency].Buy tp.Last = result[currency].Last tp.Low = result[currency].Low - tp.Volume = result[currency].Vol_cur + tp.Volume = result[currency].VolumeCurrent ticker.ProcessTicker(w.Name, x, tp, assetType) } return ticker.GetTicker(w.Name, p, assetType) @@ -93,7 +93,7 @@ func (w *WEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook. } // GetExchangeAccountInfo retrieves balances for all enabled currencies for the -// BTCE exchange +// WEX exchange func (w *WEX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo response.ExchangeName = w.GetName() From eef13c451511c5b2113fafdf81c9ee726452819d Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 31 Aug 2017 16:01:28 +1000 Subject: [PATCH 28/32] Fixed minor linter issues in coinut. Fixed linter issues, increased code cov & expanded functionality in gdax. Added new function in nonce package. --- exchanges/coinut/coinut.go | 75 +-- exchanges/coinut/coinut_test.go | 37 ++ exchanges/gdax/gdax.go | 790 +++++++++++++++++++++++-------- exchanges/gdax/gdax_test.go | 300 ++++++++++++ exchanges/gdax/gdax_types.go | 378 +++++++++++---- exchanges/gdax/gdax_websocket.go | 12 +- exchanges/gdax/gdax_wrapper.go | 2 +- exchanges/nonce/nonce.go | 13 + 8 files changed, 1261 insertions(+), 346 deletions(-) create mode 100644 exchanges/coinut/coinut_test.go create mode 100644 exchanges/gdax/gdax_test.go diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 273c6f09..969f74ec 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -15,31 +15,33 @@ import ( ) const ( - COINUT_API_URL = "https://api.coinut.com" - COINUT_API_VERSION = "1" - COINUT_INSTRUMENTS = "inst_list" - COINUT_TICKER = "inst_tick" - COINUT_ORDERBOOK = "inst_order_book" - COINUT_TRADES = "inst_trade" - COINUT_BALANCE = "user_balance" - COINUT_ORDER = "new_order" - COINUT_ORDERS = "new_orders" - COINUT_ORDERS_OPEN = "user_open_orders" - COINUT_ORDER_CANCEL = "cancel_order" - COINUT_ORDERS_CANCEL = "cancel_orders" - COINUT_TRADE_HISTORY = "trade_history" - COINUT_INDEX_TICKER = "index_tick" - COINUT_OPTION_CHAIN = "option_chain" - COINUT_POSITION_HISTORY = "position_history" - COINUT_POSITION_OPEN = "user_open_positions" + coinutAPIURL = "https://api.coinut.com" + coinutAPIVersion = "1" + coinutInstruments = "inst_list" + coinutTicker = "inst_tick" + coinutOrderbook = "inst_order_book" + coinutTrades = "inst_trade" + coinutBalance = "user_balance" + coinutOrder = "new_order" + coinutOrders = "new_orders" + coinutOrdersOpen = "user_open_orders" + coinutOrderCancel = "cancel_order" + coinutOrdersCancel = "cancel_orders" + coinutTradeHistory = "trade_history" + coinutIndexTicker = "index_tick" + coinutOptionChain = "option_chain" + coinutPositionHistory = "position_history" + coinutPositionOpen = "user_open_positions" ) +// COINUT is the overarching type across the coinut package type COINUT struct { exchange.Base WebsocketConn *websocket.Conn InstrumentMap map[string]int } +// SetDefaults sets current default values func (c *COINUT) SetDefaults() { c.Name = "COINUT" c.Enabled = false @@ -56,6 +58,7 @@ func (c *COINUT) SetDefaults() { c.AssetTypes = []string{ticker.Spot} } +// Setup sets the current exchange configuration func (c *COINUT) Setup(exch config.ExchangeConfig) { if !exch.Enabled { c.SetEnabled(false) @@ -80,11 +83,12 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) { } } +// GetInstruments returns instruments func (c *COINUT) GetInstruments() (CoinutInstruments, error) { var result CoinutInstruments params := make(map[string]interface{}) params["sec_type"] = "SPOT" - err := c.SendAuthenticatedHTTPRequest(COINUT_INSTRUMENTS, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutInstruments, params, &result) if err != nil { return result, err } @@ -95,7 +99,7 @@ func (c *COINUT) GetInstrumentTicker(instrumentID int) (CoinutTicker, error) { var result CoinutTicker params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_TICKER, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutTicker, params, &result) if err != nil { return result, err } @@ -109,7 +113,7 @@ func (c *COINUT) GetInstrumentOrderbook(instrumentID, limit int) (CoinutOrderboo if limit > 0 { params["top_n"] = limit } - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERBOOK, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrderbook, params, &result) if err != nil { return result, err } @@ -120,7 +124,7 @@ func (c *COINUT) GetTrades(instrumentID int) (CoinutTrades, error) { var result CoinutTrades params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_TRADES, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutTrades, params, &result) if err != nil { return result, err } @@ -129,7 +133,7 @@ func (c *COINUT) GetTrades(instrumentID int) (CoinutTrades, error) { func (c *COINUT) GetUserBalance() (CoinutUserBalance, error) { result := CoinutUserBalance{} - err := c.SendAuthenticatedHTTPRequest(COINUT_BALANCE, nil, &result) + err := c.SendAuthenticatedHTTPRequest(coinutBalance, nil, &result) if err != nil { return result, err } @@ -148,7 +152,7 @@ func (c *COINUT) NewOrder(instrumentID int, quantity, price float64, buy bool, o } params["client_ord_id"] = orderID - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDER, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrder, params, &result) if err != nil { return result, err } @@ -159,7 +163,7 @@ func (c *COINUT) NewOrders(orders []CoinutOrder) ([]CoinutOrdersBase, error) { var result CoinutOrdersResponse params := make(map[string]interface{}) params["orders"] = orders - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS, params, &result.Data) + err := c.SendAuthenticatedHTTPRequest(coinutOrders, params, &result.Data) if err != nil { return nil, err } @@ -170,7 +174,7 @@ func (c *COINUT) GetOpenOrders(instrumentID int) ([]CoinutOrdersResponse, error) var result []CoinutOrdersResponse params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_OPEN, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrdersOpen, params, &result) if err != nil { return nil, err } @@ -182,7 +186,7 @@ func (c *COINUT) CancelOrder(instrumentID, orderID int) (bool, error) { params := make(map[string]interface{}) params["inst_id"] = instrumentID params["order_id"] = orderID - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_CANCEL, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrdersCancel, params, &result) if err != nil { return false, err } @@ -193,7 +197,7 @@ func (c *COINUT) CancelOrders(orders []CoinutCancelOrders) (CoinutCancelOrdersRe var result CoinutCancelOrdersResponse params := make(map[string]interface{}) params["entries"] = orders - err := c.SendAuthenticatedHTTPRequest(COINUT_ORDERS_CANCEL, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOrdersCancel, params, &result) if err != nil { return result, err } @@ -210,7 +214,7 @@ func (c *COINUT) GetTradeHistory(instrumentID, start, limit int) (CoinutTradeHis if limit >= 0 && start <= 100 { params["limit"] = limit } - err := c.SendAuthenticatedHTTPRequest(COINUT_TRADE_HISTORY, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutTradeHistory, params, &result) if err != nil { return result, err } @@ -221,7 +225,7 @@ func (c *COINUT) GetIndexTicker(asset string) (CoinutIndexTicker, error) { var result CoinutIndexTicker params := make(map[string]interface{}) params["asset"] = asset - err := c.SendAuthenticatedHTTPRequest(COINUT_INDEX_TICKER, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutIndexTicker, params, &result) if err != nil { return result, err } @@ -232,7 +236,7 @@ func (c *COINUT) GetDerivativeInstruments(secType string) (interface{}, error) { var result interface{} //to-do params := make(map[string]interface{}) params["sec_type"] = secType - err := c.SendAuthenticatedHTTPRequest(COINUT_INSTRUMENTS, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutInstruments, params, &result) if err != nil { return result, err } @@ -244,7 +248,7 @@ func (c *COINUT) GetOptionChain(asset, secType string, expiry int64) (CoinutOpti params := make(map[string]interface{}) params["asset"] = asset params["sec_type"] = secType - err := c.SendAuthenticatedHTTPRequest(COINUT_OPTION_CHAIN, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutOptionChain, params, &result) if err != nil { return result, err } @@ -261,7 +265,7 @@ func (c *COINUT) GetPositionHistory(secType string, start, limit int) (CoinutPos if limit >= 0 { params["limit"] = limit } - err := c.SendAuthenticatedHTTPRequest(COINUT_POSITION_HISTORY, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutPositionHistory, params, &result) if err != nil { return result, err } @@ -276,7 +280,7 @@ func (c *COINUT) GetOpenPosition(instrumentID int) ([]CoinutOpenPosition, error) params := make(map[string]interface{}) params["inst_id"] = instrumentID - err := c.SendAuthenticatedHTTPRequest(COINUT_POSITION_OPEN, params, &result) + err := c.SendAuthenticatedHTTPRequest(coinutPositionOpen, params, &result) if err != nil { return result.Positions, err } @@ -319,7 +323,10 @@ func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[stri headers["X-SIGNATURE"] = common.HexEncodeToString(hmac) headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest("POST", COINUT_API_URL, headers, bytes.NewBuffer(payload)) + resp, err := common.SendHTTPRequest("POST", coinutAPIURL, headers, bytes.NewBuffer(payload)) + if err != nil { + return err + } if c.Verbose { log.Printf("Received raw: \n%s", resp) diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go new file mode 100644 index 00000000..3d3cdb44 --- /dev/null +++ b/exchanges/coinut/coinut_test.go @@ -0,0 +1,37 @@ +package coinut + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +const ( + apiKey = "" + apiSecret = "" +) + +var c COINUT + +func TestSetDefaults(t *testing.T) { + c.SetDefaults() +} + +func TestSetup(t *testing.T) { + exch := config.ExchangeConfig{} + c.Setup(exch) + + exch.Enabled = true + exch.APIKey = apiKey + exch.APISecret = apiSecret + c.Setup(exch) +} + +// func TestGetInstruments(t *testing.T) { +// c.Verbose = true +// resp, err := c.GetInstruments() +// if err == nil { +// t.Error("Test failed - GetInstruments() error", err) +// } +// log.Println(resp) +// } diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index 1ff4f59a..f135b3e6 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -7,7 +7,6 @@ import ( "log" "net/url" "strconv" - "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" @@ -16,28 +15,46 @@ import ( ) const ( - GDAX_API_URL = "https://api.gdax.com/" - GDAX_API_VERSION = "0" - GDAX_PRODUCTS = "products" - GDAX_ORDERBOOK = "book" - GDAX_TICKER = "ticker" - GDAX_TRADES = "trades" - GDAX_HISTORY = "candles" - GDAX_STATS = "stats" - GDAX_CURRENCIES = "currencies" - GDAX_ACCOUNTS = "accounts" - GDAX_LEDGER = "ledger" - GDAX_HOLDS = "holds" - GDAX_ORDERS = "orders" - GDAX_FILLS = "fills" - GDAX_TRANSFERS = "transfers" - GDAX_REPORTS = "reports" + gdaxAPIURL = "https://api.gdax.com/" + gdaxAPIVersion = "0" + gdaxProducts = "products" + gdaxOrderbook = "book" + gdaxTicker = "ticker" + gdaxTrades = "trades" + gdaxHistory = "candles" + gdaxStats = "stats" + gdaxCurrencies = "currencies" + gdaxAccounts = "accounts" + gdaxLedger = "ledger" + gdaxHolds = "holds" + gdaxOrders = "orders" + gdaxFills = "fills" + gdaxTransfers = "transfers" + gdaxReports = "reports" + gdaxTime = "time" + gdaxMarginTransfer = "profiles/margin-transfer" + gdaxFunding = "funding" + gdaxFundingRepay = "funding/repay" + gdaxPosition = "position" + gdaxPositionClose = "position/close" + gdaxPaymentMethod = "payment-methods" + gdaxPaymentMethodDeposit = "deposits/payment-method" + gdaxDepositCoinbase = "deposits/coinbase-account" + gdaxWithdrawalPaymentMethod = "withdrawals/payment-method" + gdaxWithdrawalCoinbase = "withdrawals/coinbase" + gdaxWithdrawalCrypto = "withdrawals/crypto" + gdaxCoinbaseAccounts = "coinbase-accounts" + gdaxTrailingVolume = "users/self/trailing-volume" ) +var sometin []string + +// GDAX is the overarching type across the GDAX package type GDAX struct { exchange.Base } +// SetDefaults sets default values for the exchange func (g *GDAX) SetDefaults() { g.Name = "GDAX" g.Enabled = false @@ -54,6 +71,7 @@ func (g *GDAX) SetDefaults() { g.AssetTypes = []string{ticker.Spot} } +// Setup initialises the exchange paramaters with the current configuration func (g *GDAX) Setup(exch config.ExchangeConfig) { if !exch.Enabled { g.SetEnabled(false) @@ -78,42 +96,39 @@ func (g *GDAX) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the current fee for the exchange func (g *GDAX) GetFee(maker bool) float64 { if maker { return g.MakerFee - } else { - return g.TakerFee } + return g.TakerFee } -func (g *GDAX) GetProducts() ([]GDAXProduct, error) { - products := []GDAXProduct{} - err := common.SendHTTPGetRequest(GDAX_API_URL+GDAX_PRODUCTS, true, &products) +// GetProducts returns supported currency pairs on the exchange with specific +// information about the pair +func (g *GDAX) GetProducts() ([]Product, error) { + products := []Product{} - if err != nil { - return nil, err - } - - return products, nil + return products, + common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, &products) } +// GetOrderbook returns orderbook by currency pair and level func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { - orderbook := GDAXOrderbookResponse{} - path := "" + orderbook := OrderbookResponse{} + + path := fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook) if level > 0 { levelStr := strconv.Itoa(level) - path = fmt.Sprintf("%s/%s/%s?level=%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_ORDERBOOK, levelStr) - } else { - path = fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_ORDERBOOK) + path = fmt.Sprintf("%s/%s/%s?level=%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook, levelStr) } - err := common.SendHTTPGetRequest(path, true, &orderbook) - if err != nil { + if err := common.SendHTTPGetRequest(path, true, &orderbook); err != nil { return nil, err } if level == 3 { - ob := GDAXOrderbookL3{} + ob := OrderbookL3{} ob.Sequence = orderbook.Sequence for _, x := range orderbook.Asks { price, err := strconv.ParseFloat((x[0].(string)), 64) @@ -125,7 +140,7 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { continue } - ob.Asks = append(ob.Asks, GDAXOrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) + ob.Asks = append(ob.Asks, OrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) } for _, x := range orderbook.Bids { price, err := strconv.ParseFloat((x[0].(string)), 64) @@ -137,64 +152,65 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { continue } - ob.Bids = append(ob.Bids, GDAXOrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) - } - return ob, nil - } else { - ob := GDAXOrderbookL1L2{} - ob.Sequence = orderbook.Sequence - for _, x := range orderbook.Asks { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue - } - amount, err := strconv.ParseFloat((x[1].(string)), 64) - if err != nil { - continue - } - - ob.Asks = append(ob.Asks, GDAXOrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) - } - for _, x := range orderbook.Bids { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue - } - amount, err := strconv.ParseFloat((x[1].(string)), 64) - if err != nil { - continue - } - - ob.Bids = append(ob.Bids, GDAXOrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) + ob.Bids = append(ob.Bids, OrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) } return ob, nil } -} + ob := OrderbookL1L2{} + ob.Sequence = orderbook.Sequence + for _, x := range orderbook.Asks { + price, err := strconv.ParseFloat((x[0].(string)), 64) + if err != nil { + continue + } + amount, err := strconv.ParseFloat((x[1].(string)), 64) + if err != nil { + continue + } -func (g *GDAX) GetTicker(symbol string) (GDAXTicker, error) { - ticker := GDAXTicker{} - path := fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_TICKER) - err := common.SendHTTPGetRequest(path, true, &ticker) - - if err != nil { - return ticker, err + ob.Asks = append(ob.Asks, OrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) } - return ticker, nil -} + for _, x := range orderbook.Bids { + price, err := strconv.ParseFloat((x[0].(string)), 64) + if err != nil { + continue + } + amount, err := strconv.ParseFloat((x[1].(string)), 64) + if err != nil { + continue + } -func (g *GDAX) GetTrades(symbol string) ([]GDAXTrade, error) { - trades := []GDAXTrade{} - path := fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_TRADES) - err := common.SendHTTPGetRequest(path, true, &trades) - - if err != nil { - return nil, err + ob.Bids = append(ob.Bids, OrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) } - return trades, nil + return ob, nil } -func (g *GDAX) GetHistoricRates(symbol string, start, end, granularity int64) ([]GDAXHistory, error) { - history := []GDAXHistory{} +// GetTicker returns ticker by currency pair +// currencyPair - example "BTC-USD" +func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { + ticker := Ticker{} + path := fmt.Sprintf( + "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTicker) + + log.Println(path) + return ticker, common.SendHTTPGetRequest(path, true, &ticker) +} + +// GetTrades listd the latest trades for a product +// currencyPair - example "BTC-USD" +func (g *GDAX) GetTrades(currencyPair string) ([]Trade, error) { + trades := []Trade{} + path := fmt.Sprintf( + "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTrades) + + return trades, common.SendHTTPGetRequest(path, true, &trades) +} + +// GetHistoricRates returns historic rates for a product. Rates are returned in +// grouped buckets based on requested granularity. +func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int64) ([]History, error) { + var resp [][]interface{} + history := []History{} values := url.Values{} if start > 0 { @@ -209,97 +225,137 @@ func (g *GDAX) GetHistoricRates(symbol string, start, end, granularity int64) ([ values.Set("granularity", strconv.FormatInt(granularity, 10)) } - path := common.EncodeURLValues(fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_HISTORY), values) - err := common.SendHTTPGetRequest(path, true, &history) + path := common.EncodeURLValues( + fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxHistory), + values) - if err != nil { - return nil, err + if err := common.SendHTTPGetRequest(path, true, &resp); err != nil { + return history, err } + + for _, single := range resp { + s := History{ + Time: int64(single[0].(float64)), + Low: single[1].(float64), + High: single[2].(float64), + Open: single[3].(float64), + Close: single[4].(float64), + Volume: single[5].(float64), + } + history = append(history, s) + } + return history, nil } -func (g *GDAX) GetStats(symbol string) (GDAXStats, error) { - stats := GDAXStats{} - path := fmt.Sprintf("%s/%s/%s", GDAX_API_URL+GDAX_PRODUCTS, symbol, GDAX_STATS) - err := common.SendHTTPGetRequest(path, true, &stats) +// GetStats returns a 24 hr stat for the product. Volume is in base currency +// units. open, high, low are in quote currency units. +func (g *GDAX) GetStats(currencyPair string) (Stats, error) { + stats := Stats{} + path := fmt.Sprintf( + "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxStats) - if err != nil { - return stats, err - } - return stats, nil + return stats, common.SendHTTPGetRequest(path, true, &stats) } -func (g *GDAX) GetCurrencies() ([]GDAXCurrency, error) { - currencies := []GDAXCurrency{} - err := common.SendHTTPGetRequest(GDAX_API_URL+GDAX_CURRENCIES, true, ¤cies) +// GetCurrencies returns a list of supported currency on the exchange +// Warning: Not all currencies may be currently in use for trading. +func (g *GDAX) GetCurrencies() ([]Currency, error) { + currencies := []Currency{} - if err != nil { - return nil, err - } - return currencies, nil + return currencies, + common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, ¤cies) } -func (g *GDAX) GetAccounts() ([]GDAXAccountResponse, error) { - resp := []GDAXAccountResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", GDAX_ACCOUNTS, nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetServerTime returns the API server time +func (g *GDAX) GetServerTime() (ServerTime, error) { + serverTime := ServerTime{} + + return serverTime, + common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, &serverTime) } -func (g *GDAX) GetAccount(account string) (GDAXAccountResponse, error) { - resp := GDAXAccountResponse{} - path := fmt.Sprintf("%s/%s", GDAX_ACCOUNTS, account) - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return resp, err - } - return resp, nil +// GetAccounts returns a list of trading accounts associated with the APIKEYS +func (g *GDAX) GetAccounts() ([]AccountResponse, error) { + resp := []AccountResponse{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxAccounts, nil, &resp) } -func (g *GDAX) GetAccountHistory(accountID string) ([]GDAXAccountLedgerResponse, error) { - resp := []GDAXAccountLedgerResponse{} - path := fmt.Sprintf("%s/%s/%s", GDAX_ACCOUNTS, accountID, GDAX_LEDGER) - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetAccount returns information for a single account. Use this endpoint when +// account_id is known +func (g *GDAX) GetAccount(accountID string) (AccountResponse, error) { + resp := AccountResponse{} + path := fmt.Sprintf("%s/%s", gdaxAccounts, accountID) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (g *GDAX) GetHolds(accountID string) ([]GDAXAccountHolds, error) { - resp := []GDAXAccountHolds{} - path := fmt.Sprintf("%s/%s/%s", GDAX_ACCOUNTS, accountID, GDAX_HOLDS) - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetAccountHistory returns a list of account activity. Account activity either +// increases or decreases your account balance. Items are paginated and sorted +// latest first. +func (g *GDAX) GetAccountHistory(accountID string) ([]AccountLedgerResponse, error) { + resp := []AccountLedgerResponse{} + path := fmt.Sprintf("%s/%s/%s", gdaxAccounts, accountID, gdaxLedger) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (g *GDAX) PlaceOrder(clientRef string, price, amount float64, side string, productID, stp string) (string, error) { +// GetHolds returns the holds that are placed on an account for any active +// orders or pending withdraw requests. As an order is filled, the hold amount +// is updated. If an order is canceled, any remaining hold is removed. For a +// withdraw, once it is completed, the hold is removed. +func (g *GDAX) GetHolds(accountID string) ([]AccountHolds, error) { + resp := []AccountHolds{} + path := fmt.Sprintf("%s/%s/%s", gdaxAccounts, accountID, gdaxHolds) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) +} + +// PlaceLimitOrder places a new limit order. Orders can only be placed if the +// account has sufficient funds. Once an order is placed, account funds +// will be put on hold for the duration of the order. How much and which funds +// are put on hold depends on the order type and parameters specified. +// +// GENERAL PARAMS +// clientRef - [optional] Order ID selected by you to identify your order +// side - buy or sell +// productID - A valid product id +// stp - [optional] Self-trade prevention flag +// +// LIMIT ORDER PARAMS +// price - Price per bitcoin +// amount - Amount of BTC to buy or sell +// timeInforce - [optional] GTC, GTT, IOC, or FOK (default is GTC) +// cancelAfter - [optional] min, hour, day * Requires time_in_force to be GTT +// postOnly - [optional] Post only flag Invalid when time_in_force is IOC or FOK +func (g *GDAX) PlaceLimitOrder(clientRef string, price, amount float64, side, timeInforce, cancelAfter, productID, stp string, postOnly bool) (string, error) { + resp := GeneralizedOrderResponse{} request := make(map[string]interface{}) - - if clientRef != "" { - request["client_oid"] = clientRef - } - + request["type"] = "limit" request["price"] = strconv.FormatFloat(price, 'f', -1, 64) request["size"] = strconv.FormatFloat(amount, 'f', -1, 64) request["side"] = side request["product_id"] = productID + if cancelAfter != "" { + request["cancel_after"] = cancelAfter + } + if timeInforce != "" { + request["time_in_foce"] = timeInforce + } + if clientRef != "" { + request["client_oid"] = clientRef + } if stp != "" { request["stp"] = stp } - - type OrderResponse struct { - ID string `json:"id"` + if postOnly { + request["post_only"] = postOnly } - resp := OrderResponse{} - err := g.SendAuthenticatedHTTPRequest("POST", GDAX_ORDERS, request, &resp) + err := g.SendAuthenticatedHTTPRequest("POST", gdaxOrders, request, &resp) if err != nil { return "", err } @@ -307,98 +363,406 @@ func (g *GDAX) PlaceOrder(clientRef string, price, amount float64, side string, return resp.ID, nil } +// PlaceMarketOrder places a new market order. +// Orders can only be placed if the account has sufficient funds. Once an order +// is placed, account funds will be put on hold for the duration of the order. +// How much and which funds are put on hold depends on the order type and +// parameters specified. +// +// GENERAL PARAMS +// clientRef - [optional] Order ID selected by you to identify your order +// side - buy or sell +// productID - A valid product id +// stp - [optional] Self-trade prevention flag +// +// MARKET ORDER PARAMS +// size - [optional]* Desired amount in BTC +// funds [optional]* Desired amount of quote currency to use +// * One of size or funds is required. +func (g *GDAX) PlaceMarketOrder(clientRef string, size, funds float64, side string, productID, stp string) (string, error) { + resp := GeneralizedOrderResponse{} + request := make(map[string]interface{}) + request["side"] = side + request["product_id"] = productID + request["type"] = "market" + + if size != 0 { + request["size"] = strconv.FormatFloat(size, 'f', -1, 64) + } + if funds != 0 { + request["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) + } + if clientRef != "" { + request["client_oid"] = clientRef + } + if stp != "" { + request["stp"] = stp + } + + err := g.SendAuthenticatedHTTPRequest("POST", gdaxOrders, request, &resp) + if err != nil { + return "", err + } + + return resp.ID, nil +} + +// PlaceMarginOrder places a new market order. +// Orders can only be placed if the account has sufficient funds. Once an order +// is placed, account funds will be put on hold for the duration of the order. +// How much and which funds are put on hold depends on the order type and +// parameters specified. +// +// GENERAL PARAMS +// clientRef - [optional] Order ID selected by you to identify your order +// side - buy or sell +// productID - A valid product id +// stp - [optional] Self-trade prevention flag +// +// MARGIN ORDER PARAMS +// size - [optional]* Desired amount in BTC +// funds - [optional]* Desired amount of quote currency to use +func (g *GDAX) PlaceMarginOrder(clientRef string, size, funds float64, side string, productID, stp string) (string, error) { + resp := GeneralizedOrderResponse{} + request := make(map[string]interface{}) + request["side"] = side + request["product_id"] = productID + request["type"] = "margin" + + if size != 0 { + request["size"] = strconv.FormatFloat(size, 'f', -1, 64) + } + if funds != 0 { + request["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) + } + if clientRef != "" { + request["client_oid"] = clientRef + } + if stp != "" { + request["stp"] = stp + } + + err := g.SendAuthenticatedHTTPRequest("POST", gdaxOrders, request, &resp) + if err != nil { + return "", err + } + + return resp.ID, nil +} + +// CancelOrder cancels order by orderID func (g *GDAX) CancelOrder(orderID string) error { - path := fmt.Sprintf("%s/%s", GDAX_ORDERS, orderID) - err := g.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) - if err != nil { - return err - } - return nil + path := fmt.Sprintf("%s/%s", gdaxOrders, orderID) + + return g.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) } -func (g *GDAX) GetOrders(params url.Values) ([]GDAXOrdersResponse, error) { - path := common.EncodeURLValues(GDAX_API_URL+GDAX_ORDERS, params) - resp := []GDAXOrdersResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", common.GetURIPath(path), nil, &resp) - if err != nil { - return nil, err +// CancelAllOrders cancels all open orders on the exchange and returns and array +// of order IDs +// currencyPair - [optional] all orders for a currencyPair string will be +// canceled +func (g *GDAX) CancelAllOrders(currencyPair string) ([]string, error) { + var resp []string + request := make(map[string]interface{}) + + if len(currencyPair) != 0 { + request["product_id"] = currencyPair } - return resp, nil + return resp, g.SendAuthenticatedHTTPRequest("DELETE", gdaxOrders, request, &resp) } -func (g *GDAX) GetOrder(orderID string) (GDAXOrderResponse, error) { - path := fmt.Sprintf("%s/%s", GDAX_ORDERS, orderID) - resp := GDAXOrderResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) - if err != nil { - return resp, err +// GetOrders lists current open orders. Only open or un-settled orders are +// returned. As soon as an order is no longer open and settled, it will no +// longer appear in the default request. +// status - can be a range of "open", "pending", "done" or "active" +// currencyPair - [optional] for example "BTC-USD" +func (g *GDAX) GetOrders(status []string, currencyPair string) ([]GeneralizedOrderResponse, error) { + resp := []GeneralizedOrderResponse{} + params := url.Values{} + + for _, individualStatus := range status { + params.Add("status", individualStatus) } - return resp, nil + if len(currencyPair) != 0 { + params.Set("product_id", currencyPair) + } + + path := common.EncodeURLValues(gdaxAPIURL+gdaxOrders, params) + path = common.GetURIPath(path) + + return resp, + g.SendAuthenticatedHTTPRequest("GET", path[1:], nil, &resp) } -func (g *GDAX) GetFills(params url.Values) ([]GDAXFillResponse, error) { - path := common.EncodeURLValues(GDAX_API_URL+GDAX_FILLS, params) - resp := []GDAXFillResponse{} - err := g.SendAuthenticatedHTTPRequest("GET", common.GetURIPath(path), nil, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetOrder returns a single order by order id. +func (g *GDAX) GetOrder(orderID string) (GeneralizedOrderResponse, error) { + resp := GeneralizedOrderResponse{} + path := fmt.Sprintf("%s/%s", gdaxOrders, orderID) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (g *GDAX) Transfer(transferType string, amount float64, accountID string) error { +// GetFills returns a list of recent fills +func (g *GDAX) GetFills(orderID, currencyPair string) ([]FillResponse, error) { + resp := []FillResponse{} + params := url.Values{} + + if len(orderID) != 0 { + params.Set("order_id", orderID) + } + if len(currencyPair) != 0 { + params.Set("product_id", currencyPair) + } + if len(params.Get("order_id")) == 0 && len(params.Get("product_id")) == 0 { + return resp, errors.New("no paramaters set") + } + + path := common.EncodeURLValues(gdaxAPIURL+gdaxFills, params) + uri := common.GetURIPath(path) + + return resp, + g.SendAuthenticatedHTTPRequest("GET", uri[1:], nil, &resp) +} + +// GetFundingRecords every order placed with a margin profile that draws funding +// will create a funding record. +// +// status - "outstanding", "settled", or "rejected" +func (g *GDAX) GetFundingRecords(status string) ([]Funding, error) { + resp := []Funding{} + params := url.Values{} + params.Set("status", status) + + path := common.EncodeURLValues(gdaxAPIURL+gdaxFunding, params) + uri := common.GetURIPath(path) + + return resp, + g.SendAuthenticatedHTTPRequest("GET", uri[1:], nil, &resp) +} + +////////////////////////// Not receiving reply from server ///////////////// +// RepayFunding repays the older funding records first +// +// amount - amount of currency to repay +// currency - currency, example USD +// func (g *GDAX) RepayFunding(amount, currency string) (Funding, error) { +// resp := Funding{} +// params := make(map[string]interface{}) +// params["amount"] = amount +// params["currency"] = currency +// +// return resp, +// g.SendAuthenticatedHTTPRequest("POST", gdaxFundingRepay, params, &resp) +// } + +// MarginTransfer sends funds between a standard/default profile and a margin +// profile. +// A deposit will transfer funds from the default profile into the margin +// profile. A withdraw will transfer funds from the margin profile to the +// default profile. Withdraws will fail if they would set your margin ratio +// below the initial margin ratio requirement. +// +// amount - the amount to transfer between the default and margin profile +// transferType - either "deposit" or "withdraw" +// profileID - The id of the margin profile to deposit or withdraw from +// currency - currency to transfer, currently on "BTC" or "USD" +func (g *GDAX) MarginTransfer(amount float64, transferType, profileID, currency string) (MarginTransfer, error) { + resp := MarginTransfer{} request := make(map[string]interface{}) request["type"] = transferType request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) - request["GDAX_account_id"] = accountID + request["currency"] = currency + request["margin_profile_id"] = profileID - err := g.SendAuthenticatedHTTPRequest("POST", GDAX_TRANSFERS, request, nil) - if err != nil { - return err - } - return nil + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxMarginTransfer, request, &resp) } -func (g *GDAX) GetReport(reportType, startDate, endDate string) (GDAXReportResponse, error) { +// GetPosition returns an overview of account profile. +func (g *GDAX) GetPosition() (AccountOverview, error) { + resp := AccountOverview{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxPosition, nil, &resp) +} + +// ClosePosition closes a position and allowing you to repay position as well +// repayOnly - allows the position to be repaid +func (g *GDAX) ClosePosition(repayOnly bool) (AccountOverview, error) { + resp := AccountOverview{} + request := make(map[string]interface{}) + request["repay_only"] = repayOnly + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxPositionClose, request, &resp) +} + +// GetPayMethods returns a full list of payment methods +func (g *GDAX) GetPayMethods() ([]PaymentMethod, error) { + resp := []PaymentMethod{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxPaymentMethod, nil, &resp) +} + +// DepositViaPaymentMethod deposits funds from a payment method. See the Payment +// Methods section for retrieving your payment methods. +// +// amount - The amount to deposit +// currency - The type of currency +// paymentID - ID of the payment method +func (g *GDAX) DepositViaPaymentMethod(amount float64, currency, paymentID string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["payment_method_id"] = paymentID + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxPaymentMethodDeposit, req, &resp) +} + +// DepositViaCoinbase deposits funds from a coinbase account. Move funds between +// a Coinbase account and GDAX trading account within daily limits. Moving +// funds between Coinbase and GDAX is instant and free. See the Coinbase +// Accounts section for retrieving your Coinbase accounts. +// +// amount - The amount to deposit +// currency - The type of currency +// accountID - ID of the coinbase account +func (g *GDAX) DepositViaCoinbase(amount float64, currency, accountID string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["coinbase_account_id"] = accountID + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxDepositCoinbase, req, &resp) +} + +// WithdrawViaPaymentMethod withdraws funds to a payment method +// +// amount - The amount to withdraw +// currency - The type of currency +// paymentID - ID of the payment method +func (g *GDAX) WithdrawViaPaymentMethod(amount float64, currency, paymentID string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["payment_method_id"] = paymentID + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxWithdrawalPaymentMethod, req, &resp) +} + +///////////////////////// NO ROUTE FOUND ERROR //////////////////////////////// +// WithdrawViaCoinbase withdraws funds to a coinbase account. +// +// amount - The amount to withdraw +// currency - The type of currency +// accountID - ID of the coinbase account +// func (g *GDAX) WithdrawViaCoinbase(amount float64, currency, accountID string) (DepositWithdrawalInfo, error) { +// resp := DepositWithdrawalInfo{} +// req := make(map[string]interface{}) +// req["amount"] = amount +// req["currency"] = currency +// req["coinbase_account_id"] = accountID +// +// return resp, +// g.SendAuthenticatedHTTPRequest("POST", gdaxWithdrawalCoinbase, req, &resp) +// } + +// WithdrawCrypto withdraws funds to a crypto address +// +// amount - The amount to withdraw +// currency - The type of currency +// cryptoAddress - A crypto address of the recipient +func (g *GDAX) WithdrawCrypto(amount float64, currency, cryptoAddress string) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := make(map[string]interface{}) + req["amount"] = amount + req["currency"] = currency + req["crypto_address"] = cryptoAddress + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxWithdrawalCrypto, req, &resp) +} + +// GetCoinbaseAccounts returns a list of coinbase accounts +func (g *GDAX) GetCoinbaseAccounts() ([]CoinbaseAccounts, error) { + resp := []CoinbaseAccounts{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxCoinbaseAccounts, nil, &resp) +} + +// GetReport returns batches of historic information about your account in +// various human and machine readable forms. +// +// reportType - "fills" or "account" +// startDate - Starting date for the report (inclusive) +// endDate - Ending date for the report (inclusive) +// currencyPair - ID of the product to generate a fills report for. +// E.g. BTC-USD. *Required* if type is fills +// accountID - ID of the account to generate an account report for. *Required* +// if type is account +// format - pdf or csv (defualt is pdf) +// email - [optional] Email address to send the report to +func (g *GDAX) GetReport(reportType, startDate, endDate, currencyPair, accountID, format, email string) (Report, error) { + resp := Report{} request := make(map[string]interface{}) request["type"] = reportType request["start_date"] = startDate request["end_date"] = endDate + request["format"] = "pdf" - resp := GDAXReportResponse{} - err := g.SendAuthenticatedHTTPRequest("POST", GDAX_REPORTS, request, &resp) - if err != nil { - return resp, err + if len(currencyPair) != 0 { + request["product_id"] = currencyPair } - return resp, nil + if len(accountID) != 0 { + request["account_id"] = accountID + } + if format == "csv" { + request["format"] = format + } + if len(email) != 0 { + request["email"] = email + } + + return resp, + g.SendAuthenticatedHTTPRequest("POST", gdaxReports, request, &resp) } -func (g *GDAX) GetReportStatus(reportID string) (GDAXReportResponse, error) { - path := fmt.Sprintf("%s/%s", GDAX_REPORTS, reportID) - resp := GDAXReportResponse{} - err := g.SendAuthenticatedHTTPRequest("POST", path, nil, &resp) - if err != nil { - return resp, err - } - return resp, nil +// GetReportStatus once a report request has been accepted for processing, the +// status is available by polling the report resource endpoint. +func (g *GDAX) GetReportStatus(reportID string) (Report, error) { + resp := Report{} + path := fmt.Sprintf("%s/%s", gdaxReports, reportID) + + return resp, g.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } +// GetTrailingVolume this request will return your 30-day trailing volume for +// all products. +func (g *GDAX) GetTrailingVolume() ([]Volume, error) { + resp := []Volume{} + + return resp, + g.SendAuthenticatedHTTPRequest("GET", gdaxTrailingVolume, nil, &resp) +} + +// SendAuthenticatedHTTPRequest sends an authenticated HTTP reque func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { if !g.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) } - if g.Nonce.Get() == 0 { - g.Nonce.Set(time.Now().Unix()) - } else { - g.Nonce.Inc() - } - payload := []byte("") if params != nil { payload, err = common.JSONEncode(params) - if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } @@ -408,26 +772,34 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri } } - message := g.Nonce.String() + method + "/" + path + string(payload) + nonce := g.Nonce.Evaluate() + message := nonce + method + "/" + path + string(payload) hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) headers["CB-ACCESS-SIGN"] = common.Base64Encode([]byte(hmac)) - headers["CB-ACCESS-TIMESTAMP"] = g.Nonce.String() + headers["CB-ACCESS-TIMESTAMP"] = nonce headers["CB-ACCESS-KEY"] = g.APIKey headers["CB-ACCESS-PASSPHRASE"] = g.ClientID headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest(method, GDAX_API_URL+path, headers, bytes.NewBuffer(payload)) + resp, err := common.SendHTTPRequest(method, gdaxAPIURL+path, headers, bytes.NewBuffer(payload)) + if err != nil { + return err + } if g.Verbose { log.Printf("Received raw: \n%s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) + type initialResponse struct { + Message string `json:"message"` + } + initialCheck := initialResponse{} - if err != nil { - return errors.New("unable to JSON Unmarshal response") + err = common.JSONDecode([]byte(resp), &initialCheck) + if err == nil && len(initialCheck.Message) != 0 { + return errors.New(initialCheck.Message) } - return nil + return common.JSONDecode([]byte(resp), &result) } diff --git a/exchanges/gdax/gdax_test.go b/exchanges/gdax/gdax_test.go new file mode 100644 index 00000000..fd2ae99a --- /dev/null +++ b/exchanges/gdax/gdax_test.go @@ -0,0 +1,300 @@ +package gdax + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var g GDAX + +// Please supply your APIKeys here for better testing +const ( + apiKey = "" + apiSecret = "" + clientID = "" //passphrase you made at API CREATION +) + +func TestSetDefaults(t *testing.T) { + g.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + gdxConfig, err := cfg.GetExchangeConfig("Bitfinex") + if err != nil { + t.Error("Test Failed - GDAX Setup() init error") + } + + g.Setup(gdxConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if g.GetFee(false) == 0 { + t.Error("Test failed - GetFee() error") + } + if g.GetFee(true) != 0 { + t.Error("Test failed - GetFee() error") + } +} + +func TestGetProducts(t *testing.T) { + t.Parallel() + _, err := g.GetProducts() + if err != nil { + t.Error("Test failed - GetProducts() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := g.GetTicker("BTC-USD") + if err != nil { + t.Error("Test failed - GetTicker() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := g.GetTrades("BTC-USD") + if err != nil { + t.Error("Test failed - GetTrades() error", err) + } +} + +func TestGetHistoricRates(t *testing.T) { + t.Parallel() + _, err := g.GetHistoricRates("BTC-USD", 0, 0, 0) + if err != nil { + t.Error("Test failed - GetHistoricRates() error", err) + } +} + +func TestGetStats(t *testing.T) { + t.Parallel() + _, err := g.GetStats("BTC-USD") + if err != nil { + t.Error("Test failed - GetStats() error", err) + } +} + +func TestGetCurrencies(t *testing.T) { + t.Parallel() + _, err := g.GetCurrencies() + if err != nil { + t.Error("Test failed - GetCurrencies() error", err) + } +} + +func TestGetServerTime(t *testing.T) { + t.Parallel() + _, err := g.GetServerTime() + if err != nil { + t.Error("Test failed - GetServerTime() error", err) + } +} + +func TestGetAccounts(t *testing.T) { + t.Parallel() + _, err := g.GetAccounts() + if err == nil { + t.Error("Test failed - GetAccounts() error", err) + } +} + +func TestGetAccount(t *testing.T) { + t.Parallel() + _, err := g.GetAccount("234cb213-ac6f-4ed8-b7b6-e62512930945") + if err == nil { + t.Error("Test failed - GetAccount() error", err) + } +} + +func TestGetAccountHistory(t *testing.T) { + t.Parallel() + _, err := g.GetAccountHistory("234cb213-ac6f-4ed8-b7b6-e62512930945") + if err == nil { + t.Error("Test failed - GetAccountHistory() error", err) + } +} + +func TestGetHolds(t *testing.T) { + t.Parallel() + _, err := g.GetHolds("234cb213-ac6f-4ed8-b7b6-e62512930945") + if err == nil { + t.Error("Test failed - GetHolds() error", err) + } +} + +func TestPlaceLimitOrder(t *testing.T) { + t.Parallel() + _, err := g.PlaceLimitOrder("", 0, 0, "buy", "", "", "BTC-USD", "", false) + if err == nil { + t.Error("Test failed - PlaceLimitOrder() error", err) + } +} + +func TestPlaceMarketOrder(t *testing.T) { + t.Parallel() + _, err := g.PlaceMarketOrder("", 1, 0, "buy", "BTC-USD", "") + if err == nil { + t.Error("Test failed - PlaceMarketOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + err := g.CancelOrder("1337") + if err == nil { + t.Error("Test failed - CancelOrder() error", err) + } +} + +func TestCancelAllOrders(t *testing.T) { + t.Parallel() + _, err := g.CancelAllOrders("BTC-USD") + if err == nil { + t.Error("Test failed - CancelAllOrders() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := g.GetOrders([]string{"open", "done"}, "BTC-USD") + if err == nil { + t.Error("Test failed - GetOrders() error", err) + } +} + +func TestGetOrder(t *testing.T) { + t.Parallel() + _, err := g.GetOrder("1337") + if err == nil { + t.Error("Test failed - GetOrders() error", err) + } +} + +func TestGetFills(t *testing.T) { + t.Parallel() + _, err := g.GetFills("1337", "BTC-USD") + if err == nil { + t.Error("Test failed - GetFills() error", err) + } + _, err = g.GetFills("", "") + if err == nil { + t.Error("Test failed - GetFills() error", err) + } +} + +func TestGetFundingRecords(t *testing.T) { + t.Parallel() + _, err := g.GetFundingRecords("rejected") + if err == nil { + t.Error("Test failed - GetFundingRecords() error", err) + } +} + +// func TestRepayFunding(t *testing.T) { +// g.Verbose = true +// _, err := g.RepayFunding("1", "BTC") +// if err != nil { +// t.Error("Test failed - RepayFunding() error", err) +// } +// } + +func TestMarginTransfer(t *testing.T) { //invalid sig issue + t.Parallel() + _, err := g.MarginTransfer(1, "withdraw", "45fa9e3b-00ba-4631-b907-8a98cbdf21be", "BTC") + if err == nil { + t.Error("Test failed - MarginTransfer() error", err) + } +} + +func TestGetPosition(t *testing.T) { + t.Parallel() + _, err := g.GetPosition() + if err == nil { + t.Error("Test failed - GetPosition() error", err) + } +} + +func TestClosePosition(t *testing.T) { + t.Parallel() + _, err := g.ClosePosition(false) + if err == nil { + t.Error("Test failed - ClosePosition() error", err) + } +} + +func TestGetPayMethods(t *testing.T) { + t.Parallel() + _, err := g.GetPayMethods() + if err == nil { + t.Error("Test failed - GetPayMethods() error", err) + } +} + +func TestDepositViaPaymentMethod(t *testing.T) { + t.Parallel() + _, err := g.DepositViaPaymentMethod(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - DepositViaPaymentMethod() error", err) + } +} + +func TestDepositViaCoinbase(t *testing.T) { + t.Parallel() + _, err := g.DepositViaCoinbase(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - DepositViaCoinbase() error", err) + } +} + +func TestWithdrawViaPaymentMethod(t *testing.T) { + t.Parallel() + _, err := g.WithdrawViaPaymentMethod(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - WithdrawViaPaymentMethod() error", err) + } +} + +// func TestWithdrawViaCoinbase(t *testing.T) { // No Route found error +// _, err := g.WithdrawViaCoinbase(1, "BTC", "c13cd0fc-72ca-55e9-843b-b84ef628c198") +// if err != nil { +// t.Error("Test failed - WithdrawViaCoinbase() error", err) +// } +// } + +func TestWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := g.WithdrawCrypto(1, "BTC", "1337") + if err == nil { + t.Error("Test failed - WithdrawViaCoinbase() error", err) + } +} + +func TestGetCoinbaseAccounts(t *testing.T) { + t.Parallel() + _, err := g.GetCoinbaseAccounts() + if err == nil { + t.Error("Test failed - GetCoinbaseAccounts() error", err) + } +} + +func TestGetReportStatus(t *testing.T) { + t.Parallel() + _, err := g.GetReportStatus("1337") + if err == nil { + t.Error("Test failed - GetReportStatus() error", err) + } +} + +func TestGetTrailingVolume(t *testing.T) { + t.Parallel() + _, err := g.GetTrailingVolume() + if err == nil { + t.Error("Test failed - GetTrailingVolume() error", err) + } +} diff --git a/exchanges/gdax/gdax_types.go b/exchanges/gdax/gdax_types.go index c14894c9..76d0143d 100644 --- a/exchanges/gdax/gdax_types.go +++ b/exchanges/gdax/gdax_types.go @@ -1,13 +1,7 @@ package gdax -type GDAXTicker struct { - TradeID int64 `json:"trade_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time string `json:"time"` -} - -type GDAXProduct struct { +// Product holds product information +type Product struct { ID string `json:"id"` BaseCurrency string `json:"base_currency"` QuoteCurrency string `json:"quote_currency"` @@ -17,37 +11,16 @@ type GDAXProduct struct { DisplayName string `json:"string"` } -type GDAXOrderL1L2 struct { - Price float64 - Amount float64 - NumOrders float64 +// Ticker holds basic ticker information +type Ticker struct { + TradeID int64 `json:"trade_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time string `json:"time"` } -type GDAXOrderL3 struct { - Price float64 - Amount float64 - OrderID string -} - -type GDAXOrderbookL1L2 struct { - Sequence int64 `json:"sequence"` - Bids []GDAXOrderL1L2 `json:"bids"` - Asks []GDAXOrderL1L2 `json:"asks"` -} - -type GDAXOrderbookL3 struct { - Sequence int64 `json:"sequence"` - Bids []GDAXOrderL3 `json:"bids"` - Asks []GDAXOrderL3 `json:"asks"` -} - -type GDAXOrderbookResponse struct { - Sequence int64 `json:"sequence"` - Bids [][]interface{} `json:"bids"` - Asks [][]interface{} `json:"asks"` -} - -type GDAXTrade struct { +// Trade holds executed trade information +type Trade struct { TradeID int64 `json:"trade_id"` Price float64 `json:"price,string"` Size float64 `json:"size,string"` @@ -55,37 +28,52 @@ type GDAXTrade struct { Side string `json:"side"` } -type GDAXStats struct { +// History holds historic rate information +type History struct { + Time int64 `json:"time"` + Low float64 `json:"low"` + High float64 `json:"high"` + Open float64 `json:"open"` + Close float64 `json:"close"` + Volume float64 `json:"volume"` +} + +// Stats holds last 24 hr data for gdax +type Stats struct { Open float64 `json:"open,string"` High float64 `json:"high,string"` Low float64 `json:"low,string"` Volume float64 `json:"volume,string"` } -type GDAXCurrency struct { +// Currency holds singular currency product information +type Currency struct { ID string Name string MinSize float64 `json:"min_size,string"` } -type GDAXHistory struct { - Time int64 - Low float64 - High float64 - Open float64 - Close float64 - Volume float64 +// ServerTime holds current requested server time information +type ServerTime struct { + ISO string `json:"iso"` + Epoch float64 `json:"epoch"` } -type GDAXAccountResponse struct { - ID string `json:"id"` - Balance float64 `json:"balance,string"` - Hold float64 `json:"hold,string"` - Available float64 `json:"available,string"` - Currency string `json:"currency"` +// AccountResponse holds the details for the trading accounts +type AccountResponse struct { + ID string `json:"id"` + Currency string `json:"currency"` + Balance float64 `json:"balance,string"` + Available float64 `json:"available,string"` + Hold float64 `json:"hold,string"` + ProfileID string `json:"profile_id"` + MarginEnabled bool `json:"margin_enabled"` + FundedAmount float64 `json:"funded_amount,string"` + DefaultAmount float64 `json:"default_amount,string"` } -type GDAXAccountLedgerResponse struct { +// AccountLedgerResponse holds account history information +type AccountLedgerResponse struct { ID string `json:"id"` CreatedAt string `json:"created_at"` Amount float64 `json:"amount,string"` @@ -94,7 +82,8 @@ type GDAXAccountLedgerResponse struct { Details interface{} `json:"details"` } -type GDAXAccountHolds struct { +// AccountHolds contains the hold information about an account +type AccountHolds struct { ID string `json:"id"` AccountID string `json:"account_id"` CreatedAt string `json:"created_at"` @@ -104,48 +93,182 @@ type GDAXAccountHolds struct { Reference string `json:"ref"` } -type GDAXOrdersResponse struct { - ID string `json:"id"` - Size float64 `json:"size,string"` - Price float64 `json:"price,string"` - ProductID string `json:"product_id"` - Status string `json:"status"` - FilledSize float64 `json:"filled_size,string"` - FillFees float64 `json:"fill_fees,string"` - Settled bool `json:"settled"` - Side string `json:"side"` - CreatedAt string `json:"created_at"` +// GeneralizedOrderResponse is the generalized return type across order +// placement and information collation +type GeneralizedOrderResponse struct { + ID string `json:"id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + ProductID string `json:"product_id"` + Side string `json:"side"` + Stp string `json:"stp"` + Type string `json:"type"` + TimeInForce string `json:"time_in_force"` + PostOnly bool `json:"post_only"` + CreatedAt string `json:"created_at"` + FillFees float64 `json:"fill_fees,string"` + FilledSize float64 `json:"filled_size,string"` + ExecutedValue float64 `json:"executed_value,string"` + Status string `json:"status"` + Settled bool `json:"settled"` + Funds float64 `json:"funds,string"` + SpecifiedFunds float64 `json:"specified_funds,string"` + DoneReason string `json:"done_reason"` + DoneAt string `json:"done_at"` } -type GDAXOrderResponse struct { - ID string `json:"id"` - Size float64 `json:"size,string"` - Price float64 `json:"price,string"` - DoneReason string `json:"done_reason"` - Status string `json:"status"` - Settled bool `json:"settled"` - FilledSize float64 `json:"filled_size,string"` - ProductID string `json:"product_id"` - FillFees float64 `json:"fill_fees,string"` - Side string `json:"side"` - CreatedAt string `json:"created_at"` - DoneAt string `json:"done_at"` +// Funding holds funding data +type Funding struct { + ID string `json:"id"` + OrderID string `json:"order_id"` + ProfileID string `json:"profile_id"` + Amount float64 `json:"amount,string"` + Status string `json:"status"` + CreatedAt string `json:"created_at"` + Currency string `json:"currency"` + RepaidAmount float64 `json:"repaid_amount"` + DefaultAmount float64 `json:"default_amount,string"` + RepaidDefault bool `json:"repaid_default"` } -type GDAXFillResponse struct { - TradeID int `json:"trade_id"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - OrderID string `json:"order_id"` - CreatedAt string `json:"created_at"` - Liquidity string `json:"liquidity"` - Fee float64 `json:"fee,string"` - Settled bool `json:"settled"` - Side string `json:"side"` +// MarginTransfer holds margin transfer details +type MarginTransfer struct { + CreatedAt string `json:"created_at"` + ID string `json:"id"` + UserID string `json:"user_id"` + ProfileID string `json:"profile_id"` + MarginProfileID string `json:"margin_profile_id"` + Type string `json:"type"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + MarginAccountID string `json:"margin_account_id"` + MarginProductID string `json:"margin_product_id"` + Status string `json:"status"` + Nonce int `json:"nonce"` } -type GDAXReportResponse struct { +// AccountOverview holds account information returned from position +type AccountOverview struct { + Status string `json:"status"` + Funding struct { + MaxFundingValue float64 `json:"max_funding_value,string"` + FundingValue float64 `json:"funding_value,string"` + OldestOutstanding struct { + ID string `json:"id"` + OrderID string `json:"order_id"` + CreatedAt string `json:"created_at"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + Amount float64 `json:"amount,string"` + } `json:"oldest_outstanding"` + } `json:"funding"` + Accounts struct { + LTC Account `json:"LTC"` + ETH Account `json:"ETH"` + USD Account `json:"USD"` + BTC Account `json:"BTC"` + } `json:"accounts"` + MarginCall struct { + Active bool `json:"active"` + Price float64 `json:"price,string"` + Side string `json:"side"` + Size float64 `json:"size,string"` + Funds float64 `json:"funds,string"` + } `json:"margin_call"` + UserID string `json:"user_id"` + ProfileID string `json:"profile_id"` + Position struct { + Type string `json:"type"` + Size float64 `json:"size,string"` + Complement float64 `json:"complement,string"` + MaxSize float64 `json:"max_size,string"` + } `json:"position"` + ProductID string `json:"product_id"` +} + +// Account is a sub-type for account overview +type Account struct { + ID string `json:"id"` + Balance float64 `json:"balance,string"` + Hold float64 `json:"hold,string"` + FundedAmount float64 `json:"funded_amount,string"` + DefaultAmount float64 `json:"default_amount,string"` +} + +// PaymentMethod holds payment method information +type PaymentMethod struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Currency string `json:"currency"` + PrimaryBuy bool `json:"primary_buy"` + PrimarySell bool `json:"primary_sell"` + AllowBuy bool `json:"allow_buy"` + AllowSell bool `json:"allow_sell"` + AllowDeposits bool `json:"allow_deposits"` + AllowWithdraw bool `json:"allow_withdraw"` + Limits struct { + Buy []LimitInfo `json:"buy"` + InstantBuy []LimitInfo `json:"instant_buy"` + Sell []LimitInfo `json:"sell"` + Deposit []LimitInfo `json:"deposit"` + } `json:"limits"` +} + +// LimitInfo is a sub-type for payment method +type LimitInfo struct { + PeriodInDays int `json:"period_in_days"` + Total struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } `json:"total"` +} + +// DepositWithdrawalInfo holds returned deposit information +type DepositWithdrawalInfo struct { + ID string `json:"id"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + PayoutAt string `json:"payout_at"` +} + +// CoinbaseAccounts holds coinbase account information +type CoinbaseAccounts struct { + ID string `json:"id"` + Name string `json:"name"` + Balance float64 `json:"balance,string"` + Currency string `json:"currency"` + Type string `json:"type"` + Primary bool `json:"primary"` + Active bool `json:"active"` + WireDepositInformation struct { + AccountNumber string `json:"account_number"` + RoutingNumber string `json:"routing_number"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountry struct { + Code string `json:"code"` + Name string `json:"name"` + } `json:"bank_country"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` + } `json:"wire_deposit_information"` + SepaDepositInformation struct { + Iban string `json:"iban"` + Swift string `json:"swift"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountryName string `json:"bank_country_name"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` + } `json:"sep_deposit_information"` +} + +// Report holds historical information +type Report struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` @@ -159,12 +282,71 @@ type GDAXReportResponse struct { } `json:"params"` } -type GDAXWebsocketSubscribe struct { +// Volume type contains trailing volume information +type Volume struct { + ProductID string `json:"product_id"` + ExchangeVolume float64 `json:"exchange_volume,string"` + Volume float64 `json:"volume,string"` + RecordedAt string `json:"recorded_at"` +} + +// OrderL1L2 is a type used in layer conversion +type OrderL1L2 struct { + Price float64 + Amount float64 + NumOrders float64 +} + +// OrderL3 is a type used in layer conversion +type OrderL3 struct { + Price float64 + Amount float64 + OrderID string +} + +// OrderbookL1L2 holds level 1 and 2 order book information +type OrderbookL1L2 struct { + Sequence int64 `json:"sequence"` + Bids []OrderL1L2 `json:"bids"` + Asks []OrderL1L2 `json:"asks"` +} + +// OrderbookL3 holds level 3 order book information +type OrderbookL3 struct { + Sequence int64 `json:"sequence"` + Bids []OrderL3 `json:"bids"` + Asks []OrderL3 `json:"asks"` +} + +// OrderbookResponse is a generalized response for order books +type OrderbookResponse struct { + Sequence int64 `json:"sequence"` + Bids [][]interface{} `json:"bids"` + Asks [][]interface{} `json:"asks"` +} + +// FillResponse contains fill information from the exchange +type FillResponse struct { + TradeID int `json:"trade_id"` + ProductID string `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + OrderID string `json:"order_id"` + CreatedAt string `json:"created_at"` + Liquidity string `json:"liquidity"` + Fee float64 `json:"fee,string"` + Settled bool `json:"settled"` + Side string `json:"side"` +} + +// WebsocketSubscribe takes in subscription information +type WebsocketSubscribe struct { Type string `json:"type"` ProductID string `json:"product_id"` } -type GDAXWebsocketReceived struct { +// WebsocketReceived holds websocket received values +type WebsocketReceived struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` @@ -174,7 +356,8 @@ type GDAXWebsocketReceived struct { Side string `json:"side"` } -type GDAXWebsocketOpen struct { +// WebsocketOpen collates open orders +type WebsocketOpen struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` @@ -184,7 +367,8 @@ type GDAXWebsocketOpen struct { Side string `json:"side"` } -type GDAXWebsocketDone struct { +// WebsocketDone holds finished order information +type WebsocketDone struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` @@ -195,7 +379,8 @@ type GDAXWebsocketDone struct { RemainingSize float64 `json:"remaining_size,string"` } -type GDAXWebsocketMatch struct { +// WebsocketMatch holds match information +type WebsocketMatch struct { Type string `json:"type"` TradeID int `json:"trade_id"` Sequence int `json:"sequence"` @@ -207,7 +392,8 @@ type GDAXWebsocketMatch struct { Side string `json:"side"` } -type GDAXWebsocketChange struct { +// WebsocketChange holds change information +type WebsocketChange struct { Type string `json:"type"` Time string `json:"time"` Sequence int `json:"sequence"` diff --git a/exchanges/gdax/gdax_websocket.go b/exchanges/gdax/gdax_websocket.go index f2656451..423b2dcb 100644 --- a/exchanges/gdax/gdax_websocket.go +++ b/exchanges/gdax/gdax_websocket.go @@ -13,7 +13,7 @@ const ( ) func (g *GDAX) WebsocketSubscribe(product string, conn *websocket.Conn) error { - subscribe := GDAXWebsocketSubscribe{"subscribe", product} + subscribe := WebsocketSubscribe{"subscribe", product} json, err := common.JSONEncode(subscribe) if err != nil { return err @@ -82,35 +82,35 @@ func (g *GDAX) WebsocketClient() { log.Println(string(resp)) break case "received": - received := GDAXWebsocketReceived{} + received := WebsocketReceived{} err := common.JSONDecode(resp, &received) if err != nil { log.Println(err) continue } case "open": - open := GDAXWebsocketOpen{} + open := WebsocketOpen{} err := common.JSONDecode(resp, &open) if err != nil { log.Println(err) continue } case "done": - done := GDAXWebsocketDone{} + done := WebsocketDone{} err := common.JSONDecode(resp, &done) if err != nil { log.Println(err) continue } case "match": - match := GDAXWebsocketMatch{} + match := WebsocketMatch{} err := common.JSONDecode(resp, &match) if err != nil { log.Println(err) continue } case "change": - change := GDAXWebsocketChange{} + change := WebsocketChange{} err := common.JSONDecode(resp, &change) if err != nil { log.Println(err) diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 8ad7b404..5e6dafc3 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -113,7 +113,7 @@ func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook return orderBook, err } - obNew := orderbookNew.(GDAXOrderbookL1L2) + obNew := orderbookNew.(OrderbookL1L2) for x := range obNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go index 79ff8751..e3ace537 100644 --- a/exchanges/nonce/nonce.go +++ b/exchanges/nonce/nonce.go @@ -3,6 +3,7 @@ package nonce import ( "strconv" "sync" + "time" ) // Nonce struct holds the nonce value @@ -47,3 +48,15 @@ func (n *Nonce) String() string { n.mtx.Unlock() return result } + +// Evaluate returns a nonce while evaluating in a single locked call +func (n *Nonce) Evaluate() string { + n.mtx.Lock() + defer n.mtx.Unlock() + if n.n == 0 { + n.n = time.Now().Unix() + return strconv.FormatInt(n.n, 10) + } + n.n = n.n + 1 + return strconv.FormatInt(n.n, 10) +} From dae90a2eaa109648bdb85f8298d805e00ad4e974 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 7 Sep 2017 19:01:51 +1000 Subject: [PATCH 29/32] Fixed linter issues, increased code cov & expanded functionality in Gemini. --- exchanges/coinut/coinut_test.go | 63 +++-- exchanges/gemini/gemini.go | 387 +++++++++++++++++++++---------- exchanges/gemini/gemini_test.go | 224 ++++++++++++++++++ exchanges/gemini/gemini_types.go | 211 +++++++++++------ 4 files changed, 661 insertions(+), 224 deletions(-) create mode 100644 exchanges/gemini/gemini_test.go diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 3d3cdb44..9fead096 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -1,37 +1,32 @@ package coinut -import ( - "testing" - - "github.com/thrasher-/gocryptotrader/config" -) - -const ( - apiKey = "" - apiSecret = "" -) - -var c COINUT - -func TestSetDefaults(t *testing.T) { - c.SetDefaults() -} - -func TestSetup(t *testing.T) { - exch := config.ExchangeConfig{} - c.Setup(exch) - - exch.Enabled = true - exch.APIKey = apiKey - exch.APISecret = apiSecret - c.Setup(exch) -} - -// func TestGetInstruments(t *testing.T) { -// c.Verbose = true -// resp, err := c.GetInstruments() -// if err == nil { -// t.Error("Test failed - GetInstruments() error", err) -// } -// log.Println(resp) +// +// const ( +// apiKey = "" +// apiSecret = "" +// ) +// +// var c COINUT +// +// func TestSetDefaults(t *testing.T) { +// c.SetDefaults() // } +// +// func TestSetup(t *testing.T) { +// exch := config.ExchangeConfig{} +// c.Setup(exch) +// +// exch.Enabled = true +// exch.APIKey = apiKey +// exch.APISecret = apiSecret +// c.Setup(exch) +// } +// +// // func TestGetInstruments(t *testing.T) { +// // c.Verbose = true +// // resp, err := c.GetInstruments() +// // if err == nil { +// // t.Error("Test failed - GetInstruments() error", err) +// // } +// // log.Println(resp) +// // } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 35766c8b..7f78eac6 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -16,30 +16,88 @@ import ( ) const ( - GEMINI_API_URL = "https://api.gemini.com" - GEMINI_API_VERSION = "1" + geminiAPIURL = "https://api.gemini.com" + geminiSandboxAPIURL = "https://api.sandbox.gemini.com" + geminiAPIVersion = "1" - GEMINI_SYMBOLS = "symbols" - GEMINI_TICKER = "pubticker" - GEMINI_AUCTION = "auction" - GEMINI_AUCTION_HISTORY = "history" - GEMINI_ORDERBOOK = "book" - GEMINI_TRADES = "trades" - GEMINI_ORDERS = "orders" - GEMINI_ORDER_NEW = "order/new" - GEMINI_ORDER_CANCEL = "order/cancel" - GEMINI_ORDER_CANCEL_SESSION = "order/cancel/session" - GEMINI_ORDER_CANCEL_ALL = "order/cancel/all" - GEMINI_ORDER_STATUS = "order/status" - GEMINI_MYTRADES = "mytrades" - GEMINI_BALANCES = "balances" - GEMINI_HEARTBEAT = "heartbeat" + geminiSymbols = "symbols" + geminiTicker = "pubticker" + geminiAuction = "auction" + geminiAuctionHistory = "history" + geminiOrderbook = "book" + geminiTrades = "trades" + geminiOrders = "orders" + geminiOrderNew = "order/new" + geminiOrderCancel = "order/cancel" + geminiOrderCancelSession = "order/cancel/session" + geminiOrderCancelAll = "order/cancel/all" + geminiOrderStatus = "order/status" + geminiMyTrades = "mytrades" + geminiBalances = "balances" + geminiTradeVolume = "tradevolume" + geminiDeposit = "deposit" + geminiNewAddress = "newAddress" + geminiWithdraw = "withdraw/" + geminiHeartbeat = "heartbeat" + + // rate limits per minute + geminiPublicRate = 120 + geminiPrivateRate = 600 + + // rates limits per second + geminiPublicRateSec = 1 + geminiPrivateRateSec = 5 + + // Too many requests returns this + geminiRateError = "429" + + // Assigned API key roles on creation + geminiRoleTrader = "trader" + geminiRoleFundManager = "fundmanager" ) +// SessionID map guides +var ( + sessionAPIKey map[int]string // map[sessionID]APIKEY + sessionAPISecret map[int]string // map[sessionID]APIKEY + sessionRole map[string]string // map[sessionID]Roles + sessionHeartbeat map[int]bool // map[sessionID]RequiresHeartBeat + IsSession bool +) + +// Gemini is the overarching type across the Gemini package, create multiple +// instances with differing APIkeys for segregation of roles for authenticated +// requests & sessions by appending the session function, if sandbox test is +// needed append the sandbox function as well. type Gemini struct { exchange.Base } +// AddSession adds a new session to the gemini base +func (g *Gemini) AddSession(sessionID int, apiKey, apiSecret, role string, needsHeartbeat bool) error { + if sessionAPIKey == nil { + IsSession = true + sessionAPIKey = make(map[int]string) + sessionAPISecret = make(map[int]string) + sessionRole = make(map[string]string) + sessionHeartbeat = make(map[int]bool) + } + _, ok := sessionAPIKey[sessionID] + if ok { + return errors.New("sessionID already being used") + } + + sessionAPIKey[sessionID] = apiKey + sessionAPISecret[sessionID] = apiSecret + sessionRole[apiKey] = role + sessionHeartbeat[sessionID] = needsHeartbeat + + return nil +} + +//return session function? + +// SetDefaults sets package defaults for gemini exchange func (g *Gemini) SetDefaults() { g.Name = "Gemini" g.Enabled = false @@ -53,6 +111,7 @@ func (g *Gemini) SetDefaults() { g.AssetTypes = []string{ticker.Spot} } +// Setup sets exchange configuration paramaters func (g *Gemini) Setup(exch config.ExchangeConfig) { if !exch.Enabled { g.SetEnabled(false) @@ -77,7 +136,35 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { } } -func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { +// Session is a session manager for differing APIKeys and roles, use this for all function +// calls in this package +func (g *Gemini) Session(sessionID int) *Gemini { + g.APIUrl = geminiAPIURL + _, ok := sessionAPIKey[sessionID] + if !ok { + return nil + } + g.APIKey = sessionAPIKey[sessionID] + g.APISecret = sessionAPISecret[sessionID] + return g +} + +// Sandbox diverts the apiURL to the sandbox API for testing purposes +func (g *Gemini) Sandbox() *Gemini { + g.APIUrl = geminiSandboxAPIURL + return g +} + +// GetSymbols returns all available symbols for trading +func (g *Gemini) GetSymbols() ([]string, error) { + symbols := []string{} + path := fmt.Sprintf("%s/v%s/%s", geminiAPIURL, geminiAPIVersion, geminiSymbols) + + return symbols, common.SendHTTPGetRequest(path, true, &symbols) +} + +// GetTicker returns information about recent trading activity for the symbol +func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { type TickerResponse struct { Ask float64 `json:"ask,string"` @@ -86,9 +173,9 @@ func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { Volume map[string]interface{} } - ticker := GeminiTicker{} + ticker := Ticker{} resp := TickerResponse{} - path := fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_TICKER, currency) + path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTicker, currencyPair) err := common.SendHTTPGetRequest(path, true, &resp) if err != nil { @@ -99,7 +186,7 @@ func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { ticker.Bid = resp.Bid ticker.Last = resp.Last - ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currency[0:3]].(string), 64) + ticker.Volume.Currency, _ = strconv.ParseFloat(resp.Volume[currencyPair[0:3]].(string), 64) ticker.Volume.USD, _ = strconv.ParseFloat(resp.Volume["USD"].(string), 64) time, _ := resp.Volume["timestamp"].(float64) @@ -108,59 +195,77 @@ func (g *Gemini) GetTicker(currency string) (GeminiTicker, error) { return ticker, nil } -func (g *Gemini) GetSymbols() ([]string, error) { - symbols := []string{} - path := fmt.Sprintf("%s/v%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_SYMBOLS) - err := common.SendHTTPGetRequest(path, true, &symbols) - if err != nil { - return nil, err - } - return symbols, nil +// GetOrderbook returns the current order book, as two arrays, one of bids, and +// one of asks +// +// params - limit_bids or limit_asks [OPTIONAL] default 50, 0 returns all Values +// Type is an integer ie "params.Set("limit_asks", 30)" +func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiOrderbook, currencyPair), params) + orderbook := Orderbook{} + + return orderbook, common.SendHTTPGetRequest(path, true, &orderbook) } -func (g *Gemini) GetAuction(currency string) (GeminiAuction, error) { - path := fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_AUCTION, currency) - auction := GeminiAuction{} - err := common.SendHTTPGetRequest(path, true, &auction) - if err != nil { - return auction, err - } - return auction, nil +// GetTrades eturn the trades that have executed since the specified timestamp. +// Timestamps are either seconds or milliseconds since the epoch (1970-01-01). +// +// currencyPair - example "btcusd" +// params -- +// since, timestamp [optional] +// limit_trades integer Optional. The maximum number of trades to return. +// include_breaks boolean Optional. Whether to display broken trades. False by +// default. Can be '1' or 'true' to activate +func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTrades, currencyPair), params) + trades := []Trade{} + + return trades, common.SendHTTPGetRequest(path, true, &trades) } -func (g *Gemini) GetAuctionHistory(currency string, params url.Values) ([]GeminiAuctionHistory, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_AUCTION, currency, GEMINI_AUCTION_HISTORY), params) - auctionHist := []GeminiAuctionHistory{} - err := common.SendHTTPGetRequest(path, true, &auctionHist) - if err != nil { - return nil, err - } - return auctionHist, nil +// GetAuction returns auction infomation +func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { + path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair) + auction := Auction{} + + return auction, common.SendHTTPGetRequest(path, true, &auction) } -func (g *Gemini) GetOrderbook(currency string, params url.Values) (GeminiOrderbook, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_ORDERBOOK, currency), params) - orderbook := GeminiOrderbook{} - err := common.SendHTTPGetRequest(path, true, &orderbook) - if err != nil { - return GeminiOrderbook{}, err - } +// GetAuctionHistory returns the auction events, optionally including +// publications of indicative prices, since the specific timestamp. +// +// currencyPair - example "btcusd" +// params -- [optional] +// since - [timestamp] Only returns auction events after the specified +// timestamp. +// limit_auction_results - [integer] The maximum number of auction +// events to return. +// include_indicative - [bool] Whether to include publication of +// indicative prices and quantities. +func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]AuctionHistory, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) + auctionHist := []AuctionHistory{} - return orderbook, nil + return auctionHist, common.SendHTTPGetRequest(path, true, &auctionHist) } -func (g *Gemini) GetTrades(currency string, params url.Values) ([]GeminiTrade, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", GEMINI_API_URL, GEMINI_API_VERSION, GEMINI_TRADES, currency), params) - trades := []GeminiTrade{} - err := common.SendHTTPGetRequest(path, true, &trades) - if err != nil { - return []GeminiTrade{}, err +func (g *Gemini) isCorrectSession(role string) error { + if !IsSession { + return errors.New("session not set") } - - return trades, nil + if sessionRole[g.APIKey] != role { + return errors.New("incorrect role for APIKEY cannot use this function") + } + return nil } +// NewOrder Only limit orders are supported through the API at present. +// returns order ID if successful func (g *Gemini) NewOrder(symbol string, amount, price float64, side, orderType string) (int64, error) { + if err := g.isCorrectSession(geminiRoleTrader); err != nil { + return 0, err + } + request := make(map[string]interface{}) request["symbol"] = symbol request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) @@ -168,96 +273,127 @@ func (g *Gemini) NewOrder(symbol string, amount, price float64, side, orderType request["side"] = side request["type"] = orderType - response := GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_NEW, request, &response) + response := Order{} + err := g.SendAuthenticatedHTTPRequest("POST", geminiOrderNew, request, &response) if err != nil { return 0, err } return response.OrderID, nil } -func (g *Gemini) CancelOrder(OrderID int64) (GeminiOrder, error) { +// CancelOrder will cancel an order. If the order is already canceled, the +// message will succeed but have no effect. +func (g *Gemini) CancelOrder(OrderID int64) (Order, error) { request := make(map[string]interface{}) request["order_id"] = OrderID - response := GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_CANCEL, request, &response) + response := Order{} + err := g.SendAuthenticatedHTTPRequest("POST", geminiOrderCancel, request, &response) if err != nil { - return GeminiOrder{}, err + return Order{}, err } return response, nil } -func (g *Gemini) CancelOrders(sessions bool) ([]GeminiOrderResult, error) { - response := []GeminiOrderResult{} - path := GEMINI_ORDER_CANCEL_ALL - if sessions { - path = GEMINI_ORDER_CANCEL_SESSION +// CancelOrders will cancel all outstanding orders created by all sessions owned +// by this account, including interactive orders placed through the UI. If +// sessions = true will only cancel the order that is called on this session +// asssociated with the APIKEY +func (g *Gemini) CancelOrders(CancelBySession bool) (OrderResult, error) { + response := OrderResult{} + path := geminiOrderCancelAll + if CancelBySession { + path = geminiOrderCancelSession } - err := g.SendAuthenticatedHTTPRequest("POST", path, nil, &response) - if err != nil { - return nil, err - } - return response, nil + + return response, g.SendAuthenticatedHTTPRequest("POST", path, nil, &response) } -func (g *Gemini) GetOrderStatus(orderID int64) (GeminiOrder, error) { +// GetOrderStatus returns the status for an order +func (g *Gemini) GetOrderStatus(orderID int64) (Order, error) { request := make(map[string]interface{}) request["order_id"] = orderID - response := GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDER_STATUS, request, &response) - if err != nil { - return GeminiOrder{}, err - } - return response, nil + response := Order{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiOrderStatus, request, &response) } -func (g *Gemini) GetOrders() ([]GeminiOrder, error) { - response := []GeminiOrder{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_ORDERS, nil, &response) - if err != nil { - return nil, err - } - return response, nil +// GetOrders returns active orders in the market +func (g *Gemini) GetOrders() ([]Order, error) { + response := []Order{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiOrders, nil, &response) } -func (g *Gemini) GetTradeHistory(symbol string, timestamp int64) ([]GeminiTradeHistory, error) { +// GetTradeHistory returns an array of trades that have been on the exchange +// +// currencyPair - example "btcusd" +// timestamp - [optional] Only return trades on or after this timestamp. +func (g *Gemini) GetTradeHistory(currencyPair string, timestamp int64) ([]TradeHistory, error) { + response := []TradeHistory{} request := make(map[string]interface{}) - request["symbol"] = symbol - request["timestamp"] = timestamp + request["symbol"] = currencyPair - response := []GeminiTradeHistory{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_MYTRADES, request, &response) - if err != nil { - return nil, err + if timestamp != 0 { + request["timestamp"] = timestamp } - return response, nil + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiMyTrades, request, &response) } -func (g *Gemini) GetBalances() ([]GeminiBalance, error) { - response := []GeminiBalance{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_BALANCES, nil, &response) - if err != nil { - return nil, err - } - return response, nil +// GetTradeVolume returns a multi-arrayed volume response +func (g *Gemini) GetTradeVolume() ([][]TradeVolume, error) { + response := [][]TradeVolume{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiTradeVolume, nil, &response) } -func (g *Gemini) PostHeartbeat() (bool, error) { +// GetBalances returns available balances in the supported currencies +func (g *Gemini) GetBalances() ([]Balance, error) { + response := []Balance{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiBalances, nil, &response) +} + +// GetDepositAddress returns a deposit address +func (g *Gemini) GetDepositAddress(depositAddlabel, currency string) (DepositAddress, error) { + response := DepositAddress{} + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiDeposit+"/"+currency+"/"+geminiNewAddress, nil, &response) +} + +// WithdrawCrypto withdraws crypto currency to a whitelisted address +func (g *Gemini) WithdrawCrypto(address, currency string, amount float64) (WithdrawelAddress, error) { + response := WithdrawelAddress{} + request := make(map[string]interface{}) + request["address"] = address + request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + + return response, + g.SendAuthenticatedHTTPRequest("POST", geminiWithdraw+currency, nil, &response) +} + +// PostHeartbeat sends a maintenance heartbeat to the exchange for all heartbeat +// maintaned sessions +func (g *Gemini) PostHeartbeat() (string, error) { type Response struct { - Result bool `json:"result"` + Result string `json:"result"` } - response := Response{} - err := g.SendAuthenticatedHTTPRequest("POST", GEMINI_HEARTBEAT, nil, &response) - if err != nil { - return false, err - } - return response.Result, nil + return response.Result, + g.SendAuthenticatedHTTPRequest("POST", geminiHeartbeat, nil, &response) } +// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the +// exchange and returns an error func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { if !g.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) @@ -269,8 +405,9 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st g.Nonce.Inc() } + headers := make(map[string]string) request := make(map[string]interface{}) - request["request"] = fmt.Sprintf("/v%s/%s", GEMINI_API_VERSION, path) + request["request"] = fmt.Sprintf("/v%s/%s", geminiAPIVersion, path) request["nonce"] = g.Nonce.Get() if params != nil { @@ -280,7 +417,6 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st } PayloadJSON, err := common.JSONEncode(request) - if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } @@ -291,21 +427,32 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st PayloadBase64 := common.Base64Encode(PayloadJSON) hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(g.APISecret)) - headers := make(map[string]string) + headers["X-GEMINI-APIKEY"] = g.APIKey headers["X-GEMINI-PAYLOAD"] = PayloadBase64 headers["X-GEMINI-SIGNATURE"] = common.HexEncodeToString(hmac) - resp, err := common.SendHTTPRequest(method, GEMINI_API_URL+path, headers, strings.NewReader("")) + resp, err := common.SendHTTPRequest(method, g.APIUrl+"/v1/"+path, headers, strings.NewReader("")) + if err != nil { + return err + } if g.Verbose { log.Printf("Received raw: \n%s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) + captureErr := ErrorCapture{} + if err = common.JSONDecode([]byte(resp), &captureErr); err == nil { + if len(captureErr.Message) != 0 || len(captureErr.Result) != 0 || len(captureErr.Reason) != 0 { + if captureErr.Result != "ok" { + return errors.New(captureErr.Message) + } + } + } + err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("unable to JSON Unmarshal response") + return err } return nil diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go new file mode 100644 index 00000000..4701f52a --- /dev/null +++ b/exchanges/gemini/gemini_test.go @@ -0,0 +1,224 @@ +package gemini + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var ( + g Gemini +) + +// Please enter sandbox API keys & assigned roles for better testing procedures + +const ( + apiKey1 = "" + apiSecret1 = "" + apiKeyRole1 = "" + sessionHeartBeat1 = false + + apiKey2 = "" + apiSecret2 = "" + apiKeyRole2 = "" + sessionHeartBeat2 = false +) + +func TestAddSession(t *testing.T) { + err := g.AddSession(1, apiKey1, apiSecret1, apiKeyRole1, true) + if err != nil { + t.Error("Test failed - AddSession() error") + } + err = g.AddSession(1, apiKey1, apiSecret1, apiKeyRole1, true) + if err == nil { + t.Error("Test failed - AddSession() error") + } + err = g.AddSession(2, apiKey2, apiSecret2, apiKeyRole2, false) + if err != nil { + t.Error("Test failed - AddSession() error") + } +} + +func TestSetDefaults(t *testing.T) { + g.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + geminiConfig, err := cfg.GetExchangeConfig("Gemini") + if err != nil { + t.Error("Test Failed - Gemini Setup() init error") + } + + geminiConfig.AuthenticatedAPISupport = true + + g.Setup(geminiConfig) +} + +func TestSession(t *testing.T) { + t.Parallel() + if g.Session(1) == nil { + t.Error("Test Failed - Session() error") + } + if g.Session(1337) != nil { + t.Error("Test Failed - Session() error") + } +} + +func TestSandbox(t *testing.T) { + t.Parallel() + g.APIUrl = geminiAPIURL + if g.Sandbox().APIUrl != geminiSandboxAPIURL { + t.Error("Test Failed - Sandbox() error") + } +} + +func TestGetSymbols(t *testing.T) { + t.Parallel() + _, err := g.GetSymbols() + if err != nil { + t.Error("Test Failed - GetSymbols() error", err) + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := g.GetTicker("BTCUSD") + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } + _, err = g.GetTicker("bla") + if err == nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := g.GetOrderbook("btcusd", url.Values{}) + if err != nil { + t.Error("Test Failed - GetOrderbook() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := g.GetTrades("btcusd", url.Values{}) + if err != nil { + t.Error("Test Failed - GetTrades() error", err) + } +} + +func TestGetAuction(t *testing.T) { + t.Parallel() + _, err := g.GetAuction("btcusd") + if err != nil { + t.Error("Test Failed - GetAuction() error", err) + } +} + +func TestGetAuctionHistory(t *testing.T) { + t.Parallel() + _, err := g.GetAuctionHistory("btcusd", url.Values{}) + if err != nil { + t.Error("Test Failed - GetAuctionHistory() error", err) + } +} + +func TestNewOrder(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().NewOrder("btcusd", 1, 4500, "buy", "exchange limit") + if err == nil { + t.Error("Test Failed - NewOrder() error", err) + } + _, err = g.Session(2).Sandbox().NewOrder("btcusd", 1, 4500, "buy", "exchange limit") + if err == nil { + t.Error("Test Failed - NewOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().CancelOrder(1337) + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} + +func TestCancelOrders(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().CancelOrders(false) + if err == nil { + t.Error("Test Failed - CancelOrders() error", err) + } + _, err = g.Session(2).Sandbox().CancelOrders(true) + if err == nil { + t.Error("Test Failed - CancelOrders() error", err) + } +} + +func TestGetOrderStatus(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetOrderStatus(1337) + if err == nil { + t.Error("Test Failed - GetOrderStatus() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetOrders() + if err == nil { + t.Error("Test Failed - GetOrders() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetTradeHistory("btcusd", 0) + if err == nil { + t.Error("Test Failed - GetTradeHistory() error", err) + } +} + +func TestGetTradeVolume(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetTradeVolume() + if err == nil { + t.Error("Test Failed - GetTradeVolume() error", err) + } +} + +func TestGetBalances(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetBalances() + if err == nil { + t.Error("Test Failed - GetBalances() error", err) + } +} + +func TestGetDepositAddress(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().GetDepositAddress("LOL123", "btc") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } +} + +func TestWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().WithdrawCrypto("LOL123", "btc", 1) + if err == nil { + t.Error("Test Failed - WithdrawCrypto() error", err) + } +} + +func TestPostHeartbeat(t *testing.T) { + t.Parallel() + _, err := g.Session(1).Sandbox().PostHeartbeat() + if err == nil { + t.Error("Test Failed - PostHeartbeat() error", err) + } +} diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index 68eaebbd..a6cd4aed 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -1,66 +1,7 @@ package gemini -type GeminiOrderbookEntry struct { - Price float64 `json:"price,string"` - Amount float64 `json:"amount,string"` -} - -type GeminiOrderbook struct { - Bids []GeminiOrderbookEntry `json:"bids"` - Asks []GeminiOrderbookEntry `json:"asks"` -} - -type GeminiTrade struct { - Timestamp int64 `json:"timestamp"` - TID int64 `json:"tid"` - Price float64 `json:"price"` - Amount float64 `json:"amount"` - Side string `json:"taker"` -} - -type GeminiOrder struct { - OrderID int64 `json:"order_id"` - ClientOrderID string `json:"client_order_id"` - Symbol string `json:"symbol"` - Exchange string `json:"exchange"` - Price float64 `json:"price,string"` - AvgExecutionPrice float64 `json:"avg_execution_price,string"` - Side string `json:"side"` - Type string `json:"type"` - Timestamp int64 `json:"timestamp"` - TimestampMS int64 `json:"timestampms"` - IsLive bool `json:"is_live"` - IsCancelled bool `json:"is_cancelled"` - WasForced bool `json:"was_forced"` - ExecutedAmount float64 `json:"executed_amount,string"` - RemainingAmount float64 `json:"remaining_amount,string"` - OriginalAmount float64 `json:"original_amount,string"` -} - -type GeminiOrderResult struct { - Result bool `json:"result"` -} - -type GeminiTradeHistory struct { - Price float64 `json:"price"` - Amount float64 `json:"amount"` - Timestamp int64 `json:"timestamp"` - TimestampMS int64 `json:"timestampms"` - Type string `json:"type"` - FeeCurrency string `json:"fee_currency"` - FeeAmount float64 `json:"fee_amount"` - TID int64 `json:"tid"` - OrderID int64 `json:"order_id"` - ClientOrderID string `json:"client_order_id"` -} - -type GeminiBalance struct { - Currency string `json:"currency"` - Amount float64 `json:"amount"` - Available float64 `json:"available"` -} - -type GeminiTicker struct { +// Ticker holds returned ticker data from the exchange +type Ticker struct { Ask float64 `json:"ask,string"` Bid float64 `json:"bid,string"` Last float64 `json:"last,string"` @@ -71,16 +12,47 @@ type GeminiTicker struct { } } -type GeminiAuction struct { - LastAuctionPrice float64 `json:"last_auction_price,string"` - LastAuctionQuantity float64 `json:"last_auction_quantity,string"` - LastHighestBidPrice float64 `json:"last_highest_bid_price,string"` - LastLowestAskPrice float64 `json:"last_lowest_ask_price,string"` - NextUpdateMS int64 `json:"next_update_ms"` - NextAuctionMS int64 `json:"next_auction_ms"` - LastAuctionEID int64 `json:"last_auction_eid"` +// Orderbook contains orderbook information for both bid and ask side +type Orderbook struct { + Bids []OrderbookEntry `json:"bids"` + Asks []OrderbookEntry `json:"asks"` } -type GeminiAuctionHistory struct { + +// OrderbookEntry subtype of orderbook information +type OrderbookEntry struct { + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` +} + +// Trade holds trade history for a specific currency pair +type Trade struct { + Timestamp int64 `json:"timestamp"` + Timestampms int64 `json:"timestampms"` + TID int64 `json:"tid"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Exchange string `json:"exchange"` + Side string `json:"type"` +} + +// Auction is generalized response type +type Auction struct { + LastAuctionEID int64 `json:"last_auction_eid"` + ClosedUntilMs int64 `json:"closed_until_ms"` + LastAuctionPrice float64 `json:"last_auction_price,string"` + LastAuctionQuantity float64 `json:"last_auction_quantity,string"` + LastHighestBidPrice float64 `json:"last_highest_bid_price,string"` + LastLowestAskPrice float64 `json:"last_lowest_ask_price,string"` + NextAuctionMS int64 `json:"next_auction_ms"` + NextUpdateMS int64 `json:"next_update_ms"` + MostRecentIndicativePrice float64 `json:"most_recent_indicative_price,string"` + MostRecentIndicativeQuantity float64 `json:"most_recent_indicative_quantity,string"` + MostRecentHighestBidPrice float64 `json:"most_recent_highest_bid_price,string"` + MostRecentLowestAskPrice float64 `json:"most_recent_lowest_ask_price,string"` +} + +// AuctionHistory holds auction history information +type AuctionHistory struct { AuctionID int64 `json:"auction_id"` AuctionPrice float64 `json:"auction_price,string"` AuctionQuantity float64 `json:"auction_quantity,string"` @@ -92,3 +64,102 @@ type GeminiAuctionHistory struct { TimestampMS int64 `json:"timestampms"` EventType string `json:"event_type"` } + +// OrderResult holds cancelled order information +type OrderResult struct { + Result string `json:"result"` + Details struct { + CancelledOrders []string `json:"cancelledOrders"` + CancelRejects []string `json:"cancelRejects"` + } `json:"details"` +} + +// Order contains order information +type Order struct { + OrderID int64 `json:"order_id,string"` + ID int64 `json:"id,string"` + ClientOrderID string `json:"client_order_id"` + Symbol string `json:"symbol"` + Exchange string `json:"exchange"` + Price float64 `json:"price,string"` + AvgExecutionPrice float64 `json:"avg_execution_price,string"` + Side string `json:"side"` + Type string `json:"type"` + Timestamp int64 `json:"timestamp,string"` + TimestampMS int64 `json:"timestampms"` + IsLive bool `json:"is_live"` + IsCancelled bool `json:"is_cancelled"` + IsHidden bool `json:"is_hidden"` + Options []string `json:"options"` + WasForced bool `json:"was_forced"` + ExecutedAmount float64 `json:"executed_amount,string"` + RemainingAmount float64 `json:"remaining_amount,string"` + OriginalAmount float64 `json:"original_amount,string"` +} + +// TradeHistory holds trade history information +type TradeHistory struct { + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Timestamp int64 `json:"timestamp"` + TimestampMS int64 `json:"timestampms"` + Type string `json:"type"` + FeeCurrency string `json:"fee_currency"` + FeeAmount float64 `json:"fee_amount,string"` + TID int64 `json:"tid"` + OrderID int64 `json:"order_id,string"` + Exchange string `json:"exchange"` + IsAuctionFilled bool `json:"is_auction_fill"` + ClientOrderID string `json:"client_order_id"` +} + +// TradeVolume holds Volume information +type TradeVolume struct { + AccountID int64 `json:"account_id"` + Symbol string `json:"symbol"` + BaseCurrency string `json:"base_currency"` + NotionalCurrency string `json:"notional_currency"` + Date string `json:"date_date"` + TotalVolumeBase float64 `json:"total_volume_base"` + MakerBuySellRatio float64 `json:"maker_buy_sell_ratio"` + BuyMakerBase float64 `json:"buy_maker_base"` + BuyMakerNotional float64 `json:"buy_maker_notional"` + BuyMakerCount float64 `json:"buy_maker_count"` + SellMakerBase float64 `json:"sell_maker_base"` + SellMakerNotional float64 `json:"sell_maker_notional"` + SellMakerCount float64 `json:"sell_maker_count"` + BuyTakerBase float64 `json:"buy_taker_base"` + BuyTakerNotional float64 `json:"buy_taker_notional"` + BuyTakerCount float64 `json:"buy_taker_count"` + SellTakerBase float64 `json:"sell_taker_base"` + SellTakerNotional float64 `json:"sell_taker_notional"` + SellTakerCount float64 `json:"sell_taker_count"` +} + +// Balance is a simple balance type +type Balance struct { + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + Available float64 `json:"available,string"` +} + +// DepositAddress holds assigned deposit address for a specific currency +type DepositAddress struct { + Currency string `json:"currency"` + Address string `json:"address"` + Label string `json:"label"` +} + +// WithdrawelAddress holds withdrawel information +type WithdrawelAddress struct { + Address string `json:"address"` + Amount float64 `json:"amount"` + TXHash string `json:"txHash"` +} + +// ErrorCapture is a generlized error response from the server +type ErrorCapture struct { + Result string `json:"result"` + Reason string `json:"reason"` + Message string `json:"message"` +} From 79a1911c935530c5df37cad60070e15f94a33834 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 12 Sep 2017 08:37:18 +1000 Subject: [PATCH 30/32] In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. --- common/common.go | 22 +- common/common_test.go | 6 +- currency/currency.go | 2 +- exchanges/anx/anx.go | 2 +- exchanges/bitfinex/bitfinex.go | 18 +- exchanges/bitstamp/bitstamp.go | 8 +- exchanges/bittrex/bittrex.go | 2 +- exchanges/btcc/btcc.go | 8 +- exchanges/btcmarkets/btcmarkets.go | 6 +- exchanges/gdax/gdax.go | 18 +- exchanges/gemini/gemini.go | 12 +- exchanges/huobi/huobi.go | 4 +- exchanges/itbit/itbit.go | 258 ++++++++++++----------- exchanges/itbit/itbit_test.go | 145 +++++++++++++ exchanges/itbit/itbit_types.go | 155 ++++++++++++-- exchanges/kraken/kraken.go | 16 +- exchanges/lakebtc/lakebtc.go | 6 +- exchanges/liqui/liqui.go | 8 +- exchanges/localbitcoins/localbitcoins.go | 8 +- exchanges/nonce/nonce.go | 40 +++- exchanges/nonce/nonce_test.go | 11 + exchanges/okcoin/okcoin.go | 26 +-- exchanges/poloniex/poloniex.go | 14 +- portfolio/portfolio.go | 47 ++++- 24 files changed, 601 insertions(+), 241 deletions(-) create mode 100644 exchanges/itbit/itbit_test.go diff --git a/common/common.go b/common/common.go index aa762ead..1d4da4ce 100644 --- a/common/common.go +++ b/common/common.go @@ -20,6 +20,7 @@ import ( "net/http" "net/url" "os" + "reflect" "regexp" "strconv" "strings" @@ -294,16 +295,24 @@ func SendHTTPRequest(method, path string, headers map[string]string, body io.Rea // SendHTTPGetRequest sends a simple get request using a url string & JSON // decodes the response into a struct pointer you have supplied. Returns an error // on failure. -func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { +func SendHTTPGetRequest(url string, jsonDecode, isVerbose bool, result interface{}) error { + if isVerbose { + log.Println("Raw URL: ", url) + } + res, err := http.Get(url) if err != nil { return err } if res.StatusCode != 200 { +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 log.Printf("HTTP status code: %d\n", res.StatusCode) log.Printf("URL: %s\n", url) return errors.New("status code was not 200") +======= + return fmt.Errorf("common.SendHTTPGetRequest() error: HTTP status code %d", res.StatusCode) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. } contents, err := ioutil.ReadAll(res.Body) @@ -311,16 +320,18 @@ func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { return err } + if isVerbose { + log.Println("Raw Resp: ", string(contents[:])) + } + defer res.Body.Close() if jsonDecode { - err := JSONDecode(contents, &result) + err := JSONDecode(contents, result) if err != nil { log.Println(string(contents[:])) return err } - } else { - result = &contents } return nil @@ -333,6 +344,9 @@ func JSONEncode(v interface{}) ([]byte, error) { // JSONDecode decodes JSON data into a structure func JSONDecode(data []byte, to interface{}) error { + if !StringContains(reflect.ValueOf(to).Type().String(), "*") { + return errors.New("json decode error - memory address not supplied") + } return json.Unmarshal(data, to) } diff --git a/common/common_test.go b/common/common_test.go index 31b17a0c..583b7025 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -488,15 +488,15 @@ func TestSendHTTPGetRequest(t *testing.T) { url := `https://etherchain.org/api/account/multiple/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe` result := test{} - err := SendHTTPGetRequest(url, true, &result) + err := SendHTTPGetRequest(url, true, false, &result) if err != nil { t.Errorf("Test failed - common SendHTTPGetRequest error: %s", err) } - err = SendHTTPGetRequest("DINGDONG", true, &result) + err = SendHTTPGetRequest("DINGDONG", true, false, &result) if err == nil { t.Error("Test failed - common SendHTTPGetRequest error") } - err = SendHTTPGetRequest(url, false, &result) + err = SendHTTPGetRequest(url, false, false, &result) if err != nil { t.Error("Test failed - common SendHTTPGetRequest error") } diff --git a/currency/currency.go b/currency/currency.go index 79c05d3b..0201f529 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -314,7 +314,7 @@ func FetchFixerCurrencyData() error { CurrencyStoreFixer = make(map[string]float64) - err := common.SendHTTPGetRequest(url, true, &result) + err := common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return err } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 1843a3c8..b77b9240 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -83,7 +83,7 @@ func (a *ANX) GetFee(maker bool) float64 { func (a *ANX) GetTicker(currency string) (ANXTicker, error) { var ticker ANXTicker - err := common.SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, &ticker) + err := common.SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, a.Verbose, &ticker) if err != nil { return ANXTicker{}, err } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 1c6185a4..b25ad80f 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -119,7 +119,7 @@ func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) { response := Ticker{} path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetStats returns various statistics about the requested pair @@ -127,7 +127,7 @@ func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) { response := []Stat{} path := fmt.Sprint(bitfinexAPIURL + bitfinexStats + symbol) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetFundingBook the entire margin funding book for both bids and asks sides @@ -137,7 +137,7 @@ func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { response := FundingBook{} path := fmt.Sprint(bitfinexAPIURL + bitfinexLendbook + symbol) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetOrderbook retieves the entire orderbook bid and ask price on a currency @@ -149,7 +149,7 @@ func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbo bitfinexAPIURL+bitfinexOrderbook+currencyPair, values, ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetTrades returns a list of the most recent trades for the given curencyPair @@ -160,7 +160,7 @@ func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStr bitfinexAPIURL+bitfinexTrades+currencyPair, values, ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetLendbook returns a list of the most recent funding data for the given @@ -174,7 +174,7 @@ func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, erro } path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLendbook+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetLends returns a list of the most recent funding data for the given @@ -185,7 +185,7 @@ func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) { response := []Lends{} path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLends+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetSymbols returns the avaliable currency pairs on the exchange @@ -193,7 +193,7 @@ func (b *Bitfinex) GetSymbols() ([]string, error) { products := []string{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbols) - return products, common.SendHTTPGetRequest(path, true, &products) + return products, common.SendHTTPGetRequest(path, true, b.Verbose, &products) } // GetSymbolsDetails a list of valid symbol IDs and the pair details @@ -201,7 +201,7 @@ func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) { response := []SymbolDetails{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbolsDetails) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetAccountInfo returns information about your account incl. trading fees diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index cafffa04..fa385413 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -127,7 +127,7 @@ func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) { tickerEndpoint, common.StringToLower(currency), ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetOrderbook Returns a JSON dictionary with "bids" and "asks". Each is a list @@ -149,7 +149,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { common.StringToLower(currency), ) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, b.Verbose, &resp) if err != nil { return Orderbook{}, err } @@ -204,7 +204,7 @@ func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Tr values, ) - return transactions, common.SendHTTPGetRequest(path, true, &transactions) + return transactions, common.SendHTTPGetRequest(path, true, b.Verbose, &transactions) } // GetEURUSDConversionRate returns the conversion rate between Euro and USD @@ -212,7 +212,7 @@ func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { rate := EURUSDConversionRate{} path := fmt.Sprintf("%s/%s", bitstampAPIURL, bitstampAPIEURUSD) - return rate, common.SendHTTPGetRequest(path, true, &rate) + return rate, common.SendHTTPGetRequest(path, true, b.Verbose, &rate) } // GetBalance returns full balance of currency held on the exchange diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index ab5791fb..13c878c5 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -373,7 +373,7 @@ func (b *Bittrex) HTTPRequest(path string, auth bool, values url.Values, v inter return err } } else { - if err := common.SendHTTPGetRequest(path, true, &response); err != nil { + if err := common.SendHTTPGetRequest(path, true, b.Verbose, &response); err != nil { return err } } diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index 639499e9..dd5b12d4 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -99,7 +99,7 @@ func (b *BTCC) GetTicker(currencyPair string) (Ticker, error) { resp := Response{} req := fmt.Sprintf("%sdata/ticker?market=%s", btccAPIUrl, currencyPair) - return resp.Ticker, common.SendHTTPGetRequest(req, true, &resp) + return resp.Ticker, common.SendHTTPGetRequest(req, true, b.Verbose, &resp) } // GetTradesLast24h returns the trades executed on the exchange over the past @@ -109,7 +109,7 @@ func (b *BTCC) GetTradesLast24h(currencyPair string) ([]Trade, error) { trades := []Trade{} req := fmt.Sprintf("%sdata/trades?market=%s", btccAPIUrl, currencyPair) - return trades, common.SendHTTPGetRequest(req, true, &trades) + return trades, common.SendHTTPGetRequest(req, true, b.Verbose, &trades) } // GetTradeHistory returns trade history data @@ -136,7 +136,7 @@ func (b *BTCC) GetTradeHistory(currencyPair string, limit, sinceTid int64, time req = common.EncodeURLValues(req, v) - return trades, common.SendHTTPGetRequest(req, true, &trades) + return trades, common.SendHTTPGetRequest(req, true, b.Verbose, &trades) } // GetOrderBook returns current market order book @@ -151,7 +151,7 @@ func (b *BTCC) GetOrderBook(currencyPair string, limit int) (Orderbook, error) { req = fmt.Sprintf("%sdata/orderbook?market=%s", btccAPIUrl, currencyPair) } - return result, common.SendHTTPGetRequest(req, true, &result) + return result, common.SendHTTPGetRequest(req, true, b.Verbose, &result) } func (b *BTCC) GetAccountInfo(infoType string) error { diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 4668fedd..1f6bc8e8 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -97,7 +97,7 @@ func (b *BTCMarkets) GetTicker(symbol string) (Ticker, error) { path := fmt.Sprintf("/market/%s/AUD/tick", common.StringToUpper(symbol)) return ticker, - common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &ticker) + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, b.Verbose, &ticker) } // GetOrderbook returns current orderbook @@ -107,7 +107,7 @@ func (b *BTCMarkets) GetOrderbook(symbol string) (Orderbook, error) { path := fmt.Sprintf("/market/%s/AUD/orderbook", common.StringToUpper(symbol)) return orderbook, - common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &orderbook) + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, b.Verbose, &orderbook) } // GetTrades returns executed trades on the exchange @@ -117,7 +117,7 @@ func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]Trade, error trades := []Trade{} path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", btcMarketsAPIURL, symbol), values) - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, b.Verbose, &trades) } // NewOrder requests a new order and returns an ID diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index f135b3e6..0749ade9 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -110,7 +110,7 @@ func (g *GDAX) GetProducts() ([]Product, error) { products := []Product{} return products, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, &products) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, g.Verbose, &products) } // GetOrderbook returns orderbook by currency pair and level @@ -123,7 +123,7 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { path = fmt.Sprintf("%s/%s/%s?level=%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook, levelStr) } - if err := common.SendHTTPGetRequest(path, true, &orderbook); err != nil { + if err := common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook); err != nil { return nil, err } @@ -193,7 +193,7 @@ func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTicker) log.Println(path) - return ticker, common.SendHTTPGetRequest(path, true, &ticker) + return ticker, common.SendHTTPGetRequest(path, true, g.Verbose, &ticker) } // GetTrades listd the latest trades for a product @@ -203,7 +203,7 @@ func (g *GDAX) GetTrades(currencyPair string) ([]Trade, error) { path := fmt.Sprintf( "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTrades) - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } // GetHistoricRates returns historic rates for a product. Rates are returned in @@ -229,7 +229,7 @@ func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxHistory), values) - if err := common.SendHTTPGetRequest(path, true, &resp); err != nil { + if err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp); err != nil { return history, err } @@ -255,7 +255,7 @@ func (g *GDAX) GetStats(currencyPair string) (Stats, error) { path := fmt.Sprintf( "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxStats) - return stats, common.SendHTTPGetRequest(path, true, &stats) + return stats, common.SendHTTPGetRequest(path, true, g.Verbose, &stats) } // GetCurrencies returns a list of supported currency on the exchange @@ -264,7 +264,7 @@ func (g *GDAX) GetCurrencies() ([]Currency, error) { currencies := []Currency{} return currencies, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, ¤cies) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, g.Verbose, ¤cies) } // GetServerTime returns the API server time @@ -272,7 +272,7 @@ func (g *GDAX) GetServerTime() (ServerTime, error) { serverTime := ServerTime{} return serverTime, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, &serverTime) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, g.Verbose, &serverTime) } // GetAccounts returns a list of trading accounts associated with the APIKEYS @@ -772,7 +772,7 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri } } - nonce := g.Nonce.Evaluate() + nonce := g.Nonce.GetValue(g.Name, false).String() message := nonce + method + "/" + path + string(payload) hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 7f78eac6..e83585f5 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -160,7 +160,7 @@ func (g *Gemini) GetSymbols() ([]string, error) { symbols := []string{} path := fmt.Sprintf("%s/v%s/%s", geminiAPIURL, geminiAPIVersion, geminiSymbols) - return symbols, common.SendHTTPGetRequest(path, true, &symbols) + return symbols, common.SendHTTPGetRequest(path, true, g.Verbose, &symbols) } // GetTicker returns information about recent trading activity for the symbol @@ -177,7 +177,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { resp := TickerResponse{} path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTicker, currencyPair) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp) if err != nil { return ticker, err } @@ -204,7 +204,7 @@ func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiOrderbook, currencyPair), params) orderbook := Orderbook{} - return orderbook, common.SendHTTPGetRequest(path, true, &orderbook) + return orderbook, common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook) } // GetTrades eturn the trades that have executed since the specified timestamp. @@ -220,7 +220,7 @@ func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, err path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTrades, currencyPair), params) trades := []Trade{} - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } // GetAuction returns auction infomation @@ -228,7 +228,7 @@ func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair) auction := Auction{} - return auction, common.SendHTTPGetRequest(path, true, &auction) + return auction, common.SendHTTPGetRequest(path, true, g.Verbose, &auction) } // GetAuctionHistory returns the auction events, optionally including @@ -246,7 +246,7 @@ func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]Au path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) auctionHist := []AuctionHistory{} - return auctionHist, common.SendHTTPGetRequest(path, true, &auctionHist) + return auctionHist, common.SendHTTPGetRequest(path, true, g.Verbose, &auctionHist) } func (g *Gemini) isCorrectSession(role string) error { diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f03517c4..9d31a34a 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -68,7 +68,7 @@ func (h *HUOBI) GetFee() float64 { func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { resp := HuobiTickerResponse{} path := fmt.Sprintf("https://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) if err != nil { return HuobiTicker{}, err @@ -79,7 +79,7 @@ func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { func (h *HUOBI) GetOrderBook(symbol string) (HuobiOrderbook, error) { path := fmt.Sprintf("https://api.huobi.com/staticmarket/depth_%s_json.js", symbol) resp := HuobiOrderbook{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) if err != nil { return resp, err } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 80aec2b0..49fc986a 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -16,14 +16,26 @@ import ( ) const ( - ITBIT_API_URL = "https://api.itbit.com/v1" - ITBIT_API_VERSION = "1" + itbitAPIURL = "https://api.itbit.com/v1" + itbitAPIVersion = "1" + itbitMarkets = "markets" + itbitOrderbook = "order_book" + itbitTicker = "ticker" + itbitWallets = "wallets" + itbitBalances = "balances" + itbitTrades = "trades" + itbitFundingHistory = "funding_history" + itbitOrders = "orders" + itbitCryptoDeposits = "cryptocurrency_deposits" + itbitWalletTransfer = "wallet_transfers" ) +// ItBit is the overarching type across the ItBit package type ItBit struct { exchange.Base } +// SetDefaults sets the defaults for the exchange func (i *ItBit) SetDefaults() { i.Name = "ITBIT" i.Enabled = false @@ -39,6 +51,7 @@ func (i *ItBit) SetDefaults() { i.AssetTypes = []string{ticker.Spot} } +// Setup sets the exchange paramaters from exchange config func (i *ItBit) Setup(exch config.ExchangeConfig) { if !exch.Enabled { i.SetEnabled(false) @@ -63,6 +76,7 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the maker or taker fee func (i *ItBit) GetFee(maker bool) float64 { if maker { return i.MakerFee @@ -70,98 +84,103 @@ func (i *ItBit) GetFee(maker bool) float64 { return i.TakerFee } -func (i *ItBit) GetTicker(currency string) (Ticker, error) { - path := ITBIT_API_URL + "/markets/" + currency + "/ticker" - var itbitTicker Ticker - err := common.SendHTTPGetRequest(path, true, &itbitTicker) - if err != nil { - return Ticker{}, err - } - return itbitTicker, nil +// GetTicker returns ticker info for a specified market. +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +func (i *ItBit) GetTicker(currencyPair string) (Ticker, error) { + var response Ticker + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, itbitTicker) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetOrderbook(currency string) (OrderbookResponse, error) { +// GetOrderbook returns full order book for the specified market. +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +func (i *ItBit) GetOrderbook(currencyPair string) (OrderbookResponse, error) { response := OrderbookResponse{} - path := ITBIT_API_URL + "/markets/" + currency + "/order_book" - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return OrderbookResponse{}, err - } - return response, nil + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, itbitOrderbook) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetTradeHistory(currency, timestamp string) bool { - req := "/trades?since=" + timestamp - err := common.SendHTTPGetRequest(ITBIT_API_URL+"markets/"+currency+req, true, nil) - if err != nil { - log.Println(err) - return false - } - return true +// GetTradeHistory returns recent trades for a specified market. +// +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +// timestamp - matchNumber, only executions after this will be returned +func (i *ItBit) GetTradeHistory(currencyPair, timestamp string) (Trades, error) { + response := Trades{} + req := "trades?since=" + timestamp + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, req) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetWallets(params url.Values) { +// GetWallets returns information about all wallets associated with the account. +// +// params -- +// page - [optional] page to return example 1. default 1 +// perPage - [optional] items per page example 50, default 50 max 50 +func (i *ItBit) GetWallets(params url.Values) ([]Wallet, error) { + resp := []Wallet{} params.Set("userId", i.ClientID) - path := "/wallets?" + params.Encode() + path := fmt.Sprintf("/%s?%s", itbitWallets, params.Encode()) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) CreateWallet(walletName string) { - path := "/wallets" +// CreateWallet creates a new wallet with a specified name. +func (i *ItBit) CreateWallet(walletName string) (Wallet, error) { + resp := Wallet{} params := make(map[string]interface{}) params["userId"] = i.ClientID params["name"] = walletName - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, + i.SendAuthenticatedHTTPRequest("POST", "/"+itbitWallets, params, &resp) } -func (i *ItBit) GetWallet(walletID string) { - path := "/wallets/" + walletID - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWallet returns wallet information by walletID +func (i *ItBit) GetWallet(walletID string) (Wallet, error) { + resp := Wallet{} + path := fmt.Sprintf("/%s/%s", itbitWallets, walletID) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletBalance(walletID, currency string) { - path := "/wallets/ " + walletID + "/balances/" + currency - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWalletBalance returns balance information for a specific currency in a +// wallet. +func (i *ItBit) GetWalletBalance(walletID, currency string) (Balance, error) { + resp := Balance{} + path := fmt.Sprintf("/%s/%s/%s/%s", itbitWallets, walletID, itbitBalances, currency) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletTrades(walletID string, params url.Values) { - path := common.EncodeURLValues("/wallets/"+walletID+"/trades", params) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWalletTrades returns all trades for a specified wallet. +func (i *ItBit) GetWalletTrades(walletID string, params url.Values) (Records, error) { + resp := Records{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitTrades) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletOrders(walletID string, params url.Values) { - path := common.EncodeURLValues("/wallets/"+walletID+"/orders", params) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetFundingHistory returns all funding history for a specified wallet. +func (i *ItBit) GetFundingHistory(walletID string, params url.Values) (FundingRecords, error) { + resp := FundingRecords{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitFundingHistory) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) PlaceWalletOrder(walletID, side, orderType, currency string, amount, price float64, instrument string, clientRef string) { - path := "/wallets/" + walletID + "/orders" +// PlaceOrder places a new order +func (i *ItBit) PlaceOrder(walletID, side, orderType, currency string, amount, price float64, instrument, clientRef string) (Order, error) { + resp := Order{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitOrders) + params := make(map[string]interface{}) params["side"] = side params["type"] = orderType @@ -174,85 +193,58 @@ func (i *ItBit) PlaceWalletOrder(walletID, side, orderType, currency string, amo params["clientOrderIdentifier"] = clientRef } - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) GetWalletOrder(walletID, orderID string) { - path := "/wallets/" + walletID + "/orders/" + orderID - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetOrder returns an order by id. +func (i *ItBit) GetOrder(walletID string, params url.Values) (Order, error) { + resp := Order{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitOrders) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) CancelWalletOrder(walletID, orderID string) { - path := "/wallets/" + walletID + "/orders/" + orderID - err := i.SendAuthenticatedHTTPRequest("DELETE", path, nil) +// CancelOrder cancels and open order. *This is not a guarantee that the order +// has been cancelled!* +func (i *ItBit) CancelOrder(walletID, orderID string) error { + path := fmt.Sprintf("/%s/%s/%s/%s", itbitWallets, walletID, itbitOrders, orderID) - if err != nil { - log.Println(err) - } + return i.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) } -func (i *ItBit) PlaceWithdrawalRequest(walletID, currency, address string, amount float64) { - path := "/wallets/" + walletID + "/cryptocurrency_withdrawals" - params := make(map[string]interface{}) - params["currency"] = currency - params["amount"] = amount - params["address"] = address - - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } -} - -func (i *ItBit) GetDepositAddress(walletID, currency string) { - path := "/wallets/" + walletID + "/cryptocurrency_deposits" +// GetDepositAddress returns a deposit address to send cryptocurrency to. +func (i *ItBit) GetDepositAddress(walletID, currency string) (CryptoCurrencyDeposit, error) { + resp := CryptoCurrencyDeposit{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitCryptoDeposits) params := make(map[string]interface{}) params["currency"] = currency - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount float64, currency string) { - path := "/wallets/" + walletID + "/wallet_transfers" +// WalletTransfer transfers funds between wallets. +func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount float64, currency string) (WalletTransfer, error) { + resp := WalletTransfer{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitWalletTransfer) + params := make(map[string]interface{}) params["sourceWalletId"] = sourceWallet params["destinationWalletId"] = destWallet params["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) params["currencyCode"] = currency - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}) (err error) { +// SendAuthenticatedHTTPRequest sends an authenticated request to itBit +func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}, result interface{}) error { if !i.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, i.Name) } - if i.Nonce.Get() == 0 { - i.Nonce.Set(time.Now().UnixNano()) - } else { - i.Nonce.Inc() - } - request := make(map[string]interface{}) - url := ITBIT_API_URL + path + url := itbitAPIURL + path if params != nil { for key, value := range params { @@ -261,12 +253,13 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } PayloadJSON := []byte("") + var err error if params != nil { - PayloadJSON, err = common.JSONEncode(request) + PayloadJSON, err = common.JSONEncode(request) if err != nil { - return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON Marshal request") + return err } if i.Verbose { @@ -274,26 +267,37 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } } - message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), i.Nonce.String(), i.Nonce.String()[0:13]}) + nonce := i.Nonce.GetValue(i.Name, false).String() + timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) + + message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), nonce, timestamp}) if err != nil { - log.Println(err) - return + return err } - hash := common.GetSHA256([]byte(i.Nonce.String() + string(message))) + hash := common.GetSHA256([]byte(nonce + string(message))) hmac := common.GetHMAC(common.HashSHA512, []byte(url+string(hash)), []byte(i.APISecret)) signature := common.Base64Encode(hmac) headers := make(map[string]string) headers["Authorization"] = i.ClientID + ":" + signature - headers["X-Auth-Timestamp"] = i.Nonce.String()[0:13] - headers["X-Auth-Nonce"] = i.Nonce.String() + headers["X-Auth-Timestamp"] = timestamp + headers["X-Auth-Nonce"] = nonce headers["Content-Type"] = "application/json" resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJSON))) + if err != nil { + return err + } if i.Verbose { log.Printf("Received raw: \n%s\n", resp) } - return nil + + errCapture := GeneralReturn{} + if err := common.JSONDecode([]byte(resp), &errCapture); err == nil { + return errors.New(errCapture.Description) + } + + return common.JSONDecode([]byte(resp), result) } diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go new file mode 100644 index 00000000..88c51689 --- /dev/null +++ b/exchanges/itbit/itbit_test.go @@ -0,0 +1,145 @@ +package itbit + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var i ItBit + +// Please provide your own keys to do proper testing +const ( + apiKey = "" + apiSecret = "" + clientID = "" +) + +func TestSetDefaults(t *testing.T) { + i.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + itbitConfig, err := cfg.GetExchangeConfig("ITBIT") + if err != nil { + t.Error("Test Failed - Gemini Setup() init error") + } + + itbitConfig.AuthenticatedAPISupport = true + itbitConfig.APIKey = apiKey + itbitConfig.APISecret = apiSecret + itbitConfig.ClientID = clientID + + i.Setup(itbitConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if i.GetFee(true) != -0.1 || i.GetFee(false) != 0.5 { + t.Error("Test Failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := i.GetTicker("XBTUSD") + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := i.GetOrderbook("XBTSGD") + if err != nil { + t.Error("Test Failed - GetOrderbook() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + t.Parallel() + _, err := i.GetTradeHistory("XBTUSD", "0") + if err != nil { + t.Error("Test Failed - GetTradeHistory() error", err) + } +} + +func TestGetWallets(t *testing.T) { + _, err := i.GetWallets(url.Values{}) + if err == nil { + t.Error("Test Failed - GetWallets() error", err) + } +} + +func TestCreateWallet(t *testing.T) { + _, err := i.CreateWallet("test") + if err == nil { + t.Error("Test Failed - CreateWallet() error", err) + } +} + +func TestGetWallet(t *testing.T) { + _, err := i.GetWallet("1337") + if err == nil { + t.Error("Test Failed - GetWallet() error", err) + } +} + +func TestGetWalletBalance(t *testing.T) { + _, err := i.GetWalletBalance("1337", "XRT") + if err == nil { + t.Error("Test Failed - GetWalletBalance() error", err) + } +} + +func TestGetWalletTrades(t *testing.T) { + _, err := i.GetWalletTrades("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetWalletTrades() error", err) + } +} + +func TestGetFundingHistory(t *testing.T) { + _, err := i.GetFundingHistory("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetFundingHistory() error", err) + } +} + +func TestPlaceOrder(t *testing.T) { + _, err := i.PlaceOrder("1337", "buy", "limit", "USD", 1, 0.2, "banjo", "sauce") + if err == nil { + t.Error("Test Failed - PlaceOrder() error", err) + } +} + +func TestGetOrder(t *testing.T) { + _, err := i.GetOrder("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + err := i.CancelOrder("1337", "1337order") + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} + +func TestGetDepositAddress(t *testing.T) { + _, err := i.GetDepositAddress("1337", "AUD") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } +} + +func TestWalletTransfer(t *testing.T) { + _, err := i.WalletTransfer("1337", "mywallet", "anotherwallet", 200, "USD") + if err == nil { + t.Error("Test Failed - WalletTransfer() error", err) + } +} diff --git a/exchanges/itbit/itbit_types.go b/exchanges/itbit/itbit_types.go index 9613404d..8adce526 100644 --- a/exchanges/itbit/itbit_types.go +++ b/exchanges/itbit/itbit_types.go @@ -1,26 +1,145 @@ package itbit -type Ticker struct { - Pair string - Bid float64 `json:",string"` - BidAmt float64 `json:",string"` - Ask float64 `json:",string"` - AskAmt float64 `json:",string"` - LastPrice float64 `json:",string"` - LastAmt float64 `json:",string"` - Volume24h float64 `json:",string"` - VolumeToday float64 `json:",string"` - High24h float64 `json:",string"` - Low24h float64 `json:",string"` - HighToday float64 `json:",string"` - LowToday float64 `json:",string"` - OpenToday float64 `json:",string"` - VwapToday float64 `json:",string"` - Vwap24h float64 `json:",string"` - ServertimeUTC string +// GeneralReturn is a generalized return type to capture any errors +type GeneralReturn struct { + Code int `json:"code"` + Description string `json:"description"` + RequestID string `json:"requestId"` } +// Ticker holds returned ticker information +type Ticker struct { + Pair string `json:"pair"` + Bid float64 `json:"bid,string"` + BidAmt float64 `json:"bidAmt,string"` + Ask float64 `json:"ask,string"` + AskAmt float64 `json:"askAmt,string"` + LastPrice float64 `json:"lastPrice,string"` + LastAmt float64 `json:"lastAmt,string"` + Volume24h float64 `json:"volume24h,string"` + VolumeToday float64 `json:"volumeToday,string"` + High24h float64 `json:"high24h,string"` + Low24h float64 `json:"low24h,string"` + HighToday float64 `json:"highToday,string"` + LowToday float64 `json:"lowToday,string"` + OpenToday float64 `json:"openToday,string"` + VwapToday float64 `json:"vwapToday,string"` + Vwap24h float64 `json:"vwap24h,string"` + ServertimeUTC string `json:"serverTimeUTC"` +} + +// OrderbookResponse contains multi-arrayed strings of bid and ask side +// information type OrderbookResponse struct { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } + +// Trades holds recent trades with associated information +type Trades struct { + RecentTrades []struct { + Timestamp string `json:"timestamp"` + MatchNumber int64 `json:"matchNumber"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + } `json:"recentTrades"` +} + +// Wallet contains specific wallet information +type Wallet struct { + ID string `json:"id"` + UserID string `json:"userId"` + Name string `json:"name"` + Balances []Balance `json:"balances"` +} + +// Balance is a sub type holding balance information +type Balance struct { + Currency string `json:"currency"` + AvailableBalance float64 `json:"availableBalance,string"` + TotalBalance float64 `json:"totalBalance,string"` +} + +// Records embodies records of trade history information +type Records struct { + TotalNumberOfRecords int `json:"totalNumberOfRecords,string"` + CurrentPageNumber int `json:"currentPageNumber,string"` + LatestExecutedID int64 `json:"latestExecutionId,string"` + RecordsPerPage int `json:"recordsPerPage,string"` + TradingHistory []TradeHistory `json:"tradingHistory"` +} + +// TradeHistory stores historic trade values +type TradeHistory struct { + OrderID string `json:"orderId"` + Timestamp string `json:"timestamp"` + Instrument string `json:"instrument"` + Direction string `json:"direction"` + CurrencyOne string `json:"currency1"` + CurrencyOneAmount float64 `json:"currency1Amount,string"` + CurrencyTwo string `json:"currency2"` + CurrencyTwoAmount float64 `json:"currency2Amount"` + Rate float64 `json:"rate,string"` + CommissionPaid float64 `json:"commissionPaid,string"` + CommissionCurrency string `json:"commissionCurrency"` + RebatesApplied float64 `json:"rebatesApplied,string"` + RebateCurrency string `json:"rebateCurrency"` +} + +// FundingRecords embodies records of fund history information +type FundingRecords struct { + TotalNumberOfRecords int `json:"totalNumberOfRecords,string"` + CurrentPageNumber int `json:"currentPageNumber,string"` + LatestExecutedID int64 `json:"latestExecutionId,string"` + RecordsPerPage int `json:"recordsPerPage,string"` + FundingHistory []FundHistory `json:"fundingHistory"` +} + +// FundHistory stores historic funding transactions +type FundHistory struct { + BankName string `json:"bankName"` + WithdrawalID int64 `json:"withdrawalId"` + HoldingPeriodCompletionDate string `json:"holdingPeriodCompletionDate"` + DestinationAddress string `json:"destinationAddress"` + TxnHash string `json:"txnHash"` + Time string `json:"time"` + Currency string `json:"currency"` + TransactionType string `json:"transactionType"` + Amount float64 `json:"amount,string"` + WalletName string `json:"walletName"` + Status string `json:"status"` +} + +// Order holds order information +type Order struct { + ID string `json:"id"` + WalletID string `json:"walletId"` + Side string `json:"side"` + Instrument string `json:"instrument"` + Type string `json:"type"` + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + AmountFilled float64 `json:"amountFilled,string"` + VolumeWeightedAveragePrice float64 `json:"volumeWeightedAveragePrice,string"` + CreatedTime string `json:"createdTime"` + Status string `json:"Status"` + Metadata interface{} `json:"metadata"` + ClientOrderIdentifier string `json:"clientOrderIdentifier"` +} + +// CryptoCurrencyDeposit holds information about a new wallet +type CryptoCurrencyDeposit struct { + ID int `json:"id"` + WalletID string `json:"walletID"` + DepositAddress string `json:"depositAddress"` + Metadata interface{} `json:"metadata"` +} + +// WalletTransfer holds wallet transfer information +type WalletTransfer struct { + SourceWalletID string `json:"sourceWalletId"` + DestinationWalletID string `json:"destinationWalletId"` + Amount float64 `json:"amount,string"` + CurrencyCode string `json:"currencyCode"` +} diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index cd264052..418881ef 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -99,7 +99,7 @@ func (k *Kraken) GetFee(cryptoTrade bool) float64 { func (k *Kraken) GetServerTime() error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SERVER_TIME) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -112,7 +112,7 @@ func (k *Kraken) GetServerTime() error { func (k *Kraken) GetAssets() error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSETS) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -130,7 +130,7 @@ func (k *Kraken) GetAssetPairs() (map[string]KrakenAssetPairs, error) { response := Response{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSET_PAIRS) - err := common.SendHTTPGetRequest(path, true, &response) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &response) if err != nil { return nil, err @@ -150,7 +150,7 @@ func (k *Kraken) GetTicker(symbol string) error { resp := Response{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TICKER, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &resp) if err != nil { return err @@ -183,7 +183,7 @@ func (k *Kraken) GetOHLC(symbol string) error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_OHLC, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -201,7 +201,7 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { var result interface{} var ob Orderbook path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_DEPTH, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return ob, err @@ -257,7 +257,7 @@ func (k *Kraken) GetTrades(symbol string) error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TRADES, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -273,7 +273,7 @@ func (k *Kraken) GetSpread(symbol string) { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SPREAD, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { log.Println(err) diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 62a8f604..639619d2 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -85,7 +85,7 @@ func (l *LakeBTC) GetFee(maker bool) float64 { func (l *LakeBTC) GetTicker() (map[string]LakeBTCTicker, error) { response := make(map[string]LakeBTCTickerResponse) path := fmt.Sprintf("%s/%s", LAKEBTC_API_URL, LAKEBTC_TICKER) - err := common.SendHTTPGetRequest(path, true, &response) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &response) if err != nil { return nil, err } @@ -126,7 +126,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { } path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_ORDERBOOK, common.StringToLower(currency)) resp := Response{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return LakeBTCOrderbook{}, err } @@ -165,7 +165,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { func (l *LakeBTC) GetTradeHistory(currency string) ([]LakeBTCTradeHistory, error) { path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_TRADES, common.StringToLower(currency)) resp := []LakeBTCTradeHistory{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return nil, err } diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 4611148e..f6baf699 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -102,7 +102,7 @@ func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { func (l *Liqui) GetInfo() (LiquiInfo, error) { req := fmt.Sprintf("%s/%s/%s/", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_INFO) resp := LiquiInfo{} - err := common.SendHTTPGetRequest(req, true, &resp) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &resp) if err != nil { return resp, err @@ -118,7 +118,7 @@ func (l *Liqui) GetTicker(symbol string) (map[string]LiquiTicker, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TICKER, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return nil, err @@ -134,7 +134,7 @@ func (l *Liqui) GetDepth(symbol string) (LiquiOrderbook, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_DEPTH, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return LiquiOrderbook{}, err } @@ -151,7 +151,7 @@ func (l *Liqui) GetTrades(symbol string) ([]LiquiTrades, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TRADES, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return []LiquiTrades{}, err } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 705c0aca..e07bbe1f 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -80,7 +80,7 @@ func (l *LocalBitcoins) GetFee(maker bool) float64 { func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { result := make(map[string]LocalBitcoinsTicker) - err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, &result) + err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, l.Verbose, &result) if err != nil { return result, err @@ -92,7 +92,7 @@ func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]LocalBitcoinsTrade, error) { path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency), values) result := []LocalBitcoinsTrade{} - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &result) if err != nil { return result, err @@ -109,7 +109,7 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (LocalBitcoinsOrderbook, e path := fmt.Sprintf("%s/%s/orderbook.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency) resp := response{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return LocalBitcoinsOrderbook{}, err @@ -162,7 +162,7 @@ func (l *LocalBitcoins) GetAccountInfo(username string, self bool) (LocalBitcoin } } else { path := fmt.Sprintf("%s/api/account_info/%s/", LOCALBITCOINS_API_URL, username) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return resp.Data, err diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go index e3ace537..8f87529d 100644 --- a/exchanges/nonce/nonce.go +++ b/exchanges/nonce/nonce.go @@ -8,8 +8,12 @@ import ( // Nonce struct holds the nonce value type Nonce struct { + // Standard nonce n int64 mtx sync.Mutex + // Hash table exclusive exchange specific nonce values + boundedCall map[string]int64 + boundedMtx sync.Mutex } // Inc increments the nonce value @@ -49,14 +53,32 @@ func (n *Nonce) String() string { return result } -// Evaluate returns a nonce while evaluating in a single locked call -func (n *Nonce) Evaluate() string { - n.mtx.Lock() - defer n.mtx.Unlock() - if n.n == 0 { - n.n = time.Now().Unix() - return strconv.FormatInt(n.n, 10) +// Value is a return type for GetValue +type Value int64 + +// GetValue returns a nonce value and can be set as a higher precision. Values +// stored in an exchange specific hash table using a single locked call. +func (n *Nonce) GetValue(exchName string, nanoPrecision bool) Value { + n.boundedMtx.Lock() + defer n.boundedMtx.Unlock() + + if n.boundedCall == nil { + n.boundedCall = make(map[string]int64) } - n.n = n.n + 1 - return strconv.FormatInt(n.n, 10) + + if n.boundedCall[exchName] == 0 { + if nanoPrecision { + n.boundedCall[exchName] = time.Now().UnixNano() + return Value(n.boundedCall[exchName]) + } + n.boundedCall[exchName] = time.Now().Unix() + return Value(n.boundedCall[exchName]) + } + n.boundedCall[exchName]++ + return Value(n.boundedCall[exchName]) +} + +// String is a Value method that changes format to a string +func (v Value) String() string { + return strconv.FormatInt(int64(v), 10) } diff --git a/exchanges/nonce/nonce_test.go b/exchanges/nonce/nonce_test.go index 8bf6d6da..74996d2c 100644 --- a/exchanges/nonce/nonce_test.go +++ b/exchanges/nonce/nonce_test.go @@ -1,6 +1,7 @@ package nonce import ( + "strconv" "testing" "time" ) @@ -56,6 +57,16 @@ func TestString(t *testing.T) { } } +func TestGetValue(t *testing.T) { + var nonce Nonce + timeNowNano := strconv.FormatInt(time.Now().UnixNano(), 10) + nValue := nonce.GetValue("dingdong", true).String() + + if timeNowNano == nValue { + t.Error("Test failed - GetValue() error, incorrect values") + } +} + func TestNonceConcurrency(t *testing.T) { var nonce Nonce nonce.Set(12312) diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index e9b8364b..12466207 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -152,7 +152,7 @@ func (o *OKCoin) GetTicker(symbol string) (OKCoinTicker, error) { vals := url.Values{} vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_TICKER, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return OKCoinTicker{}, err } @@ -171,7 +171,7 @@ func (o *OKCoin) GetOrderBook(symbol string, size int64, merge bool) (OKCoinOrde } path := common.EncodeURLValues(o.APIUrl+OKCOIN_DEPTH, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return resp, err } @@ -187,7 +187,7 @@ func (o *OKCoin) GetTrades(symbol string, since int64) ([]OKCoinTrades, error) { } path := common.EncodeURLValues(o.APIUrl+OKCOIN_TRADES, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return nil, err } @@ -209,7 +209,7 @@ func (o *OKCoin) GetKline(symbol, klineType string, size, since int64) ([]interf } path := common.EncodeURLValues(o.APIUrl+OKCOIN_KLINE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err } @@ -223,7 +223,7 @@ func (o *OKCoin) GetFuturesTicker(symbol, contractType string) (OKCoinFuturesTic vals.Set("symbol", symbol) vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TICKER, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return OKCoinFuturesTicker{}, err } @@ -244,7 +244,7 @@ func (o *OKCoin) GetFuturesDepth(symbol, contractType string, size int64, merge } path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_DEPTH, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return result, err } @@ -258,7 +258,7 @@ func (o *OKCoin) GetFuturesTrades(symbol, contractType string) ([]OKCoinFuturesT vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TRADES, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return nil, err } @@ -275,7 +275,7 @@ func (o *OKCoin) GetFuturesIndex(symbol string) (float64, error) { vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_INDEX, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return 0, err } @@ -288,7 +288,7 @@ func (o *OKCoin) GetFuturesExchangeRate() (float64, error) { } result := Response{} - err := common.SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, &result) + err := common.SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, o.Verbose, &result) if err != nil { return result.Rate, err } @@ -304,7 +304,7 @@ func (o *OKCoin) GetFuturesEstimatedPrice(symbol string) (float64, error) { vals := url.Values{} vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_ESTIMATED_PRICE, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return result.Price, err } @@ -326,7 +326,7 @@ func (o *OKCoin) GetFuturesKline(symbol, klineType, contractType string, size, s } path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_KLINE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err @@ -341,7 +341,7 @@ func (o *OKCoin) GetFuturesHoldAmount(symbol, contractType string) ([]OKCoinFutu vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_HOLD_AMOUNT, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err @@ -362,7 +362,7 @@ func (o *OKCoin) GetFuturesExplosive(symbol, contractType string, status, curren vals.Set("page_length", strconv.FormatInt(pageLength, 10)) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_EXPLOSIVE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 3c777206..d0b8d753 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -101,7 +101,7 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { resp := response{} path := fmt.Sprintf("%s/public?command=returnTicker", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp.Data) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) if err != nil { return resp.Data, err @@ -112,7 +112,7 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} path := fmt.Sprintf("%s/public?command=return24hVolume", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return resp, err @@ -130,7 +130,7 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo resp := PoloniexOrderbookResponse{} path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return PoloniexOrderbook{}, err @@ -173,7 +173,7 @@ func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]PoloniexT resp := []PoloniexTradeHistory{} path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err @@ -199,7 +199,7 @@ func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]Polo resp := []PoloniexChartData{} path := fmt.Sprintf("%s/public?command=returnChartData&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err @@ -213,7 +213,7 @@ func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { } resp := Response{} path := fmt.Sprintf("%s/public?command=returnCurrencies", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp.Data) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) if err != nil { return resp.Data, err @@ -224,7 +224,7 @@ func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { func (p *Poloniex) GetLoanOrders(currency string) (PoloniexLoanOrders, error) { resp := PoloniexLoanOrders{} path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", POLONIEX_API_URL, currency) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return resp, err diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index d3e52fb5..9a8d7632 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -80,7 +80,7 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { "%s/%s/%s", etherchainAPIURL, etherchainAccountMultiple, addresses, ) result := EtherchainBalanceResponse{} - err := common.SendHTTPGetRequest(url, true, &result) + err := common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return result, err } @@ -90,17 +90,62 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 // GetCryptoIDAddress queries CryptoID for an address balance for a // specified cryptocurrency func GetCryptoIDAddress(address string, coinType string) (float64, error) { ok, err := common.IsValidCryptoAddress(address, coinType) if !ok || err != nil { return 0, errors.New("invalid address") +======= +// GetBlockrBalanceSingle queries Blockr for an address balance for either a +// LTC or a BTC single address +func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { + valid, _ := common.IsValidCryptoAddress(address, coinType) + if !valid { + return BlockrAddressBalanceSingle{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) } + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, address, + ) + result := BlockrAddressBalanceSingle{} + err := common.SendHTTPGetRequest(url, true, false, &result) + if err != nil { + return result, err + } + if result.Status != "success" { + return result, errors.New(result.Message) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. + } + +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 var result interface{} url := fmt.Sprintf("%s/%s/api.dws?q=getbalance&a=%s", cryptoIDAPIURL, common.StringToLower(coinType), address) err = common.SendHTTPGetRequest(url, true, &result) +======= +// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC +// or a BTC multiple addresses +func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { + for _, add := range addresses { + valid, _ := common.IsValidCryptoAddress(add, coinType) + if !valid { + return BlockrAddressBalanceMulti{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) + } + } + addressesStr := common.JoinStrings(addresses, ",") + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, addressesStr, + ) + result := BlockrAddressBalanceMulti{} + err := common.SendHTTPGetRequest(url, true, false, &result) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. if err != nil { return 0, err } From 60fc00a5b310cbe3052f4a990dcb9ad04db8e3c6 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 13 Sep 2017 12:36:32 +1000 Subject: [PATCH 31/32] Liqui Package - Fixed linter issues and expanded code cov. --- exchanges/liqui/liqui.go | 212 +++++++++++++++------------------ exchanges/liqui/liqui_test.go | 125 +++++++++++++++++++ exchanges/liqui/liqui_types.go | 85 +++++++------ 3 files changed, 270 insertions(+), 152 deletions(-) create mode 100644 exchanges/liqui/liqui_test.go diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index f6baf699..c9f8cc2f 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -16,29 +16,31 @@ import ( ) const ( - LIQUI_API_PUBLIC_URL = "https://api.Liqui.io/api" - LIQUI_API_PRIVATE_URL = "https://api.Liqui.io/tapi" - LIQUI_API_PUBLIC_VERSION = "3" - LIQUI_API_PRIVATE_VERSION = "1" - LIQUI_INFO = "info" - LIQUI_TICKER = "ticker" - LIQUI_DEPTH = "depth" - LIQUI_TRADES = "trades" - LIQUI_ACCOUNT_INFO = "getInfo" - LIQUI_TRADE = "Trade" - LIQUI_ACTIVE_ORDERS = "ActiveOrders" - LIQUI_ORDER_INFO = "OrderInfo" - LIQUI_CANCEL_ORDER = "CancelOrder" - LIQUI_TRADE_HISTORY = "TradeHistory" - LIQUI_WITHDRAW_COIN = "WithdrawCoin" + liquiAPIPublicURL = "https://api.Liqui.io/api" + liquiAPIPrivateURL = "https://api.Liqui.io/tapi" + liquiAPIPublicVersion = "3" + liquiAPIPrivateVersion = "1" + liquiInfo = "info" + liquiTicker = "ticker" + liquiDepth = "depth" + liquiTrades = "trades" + liquiAccountInfo = "getInfo" + liquiTrade = "Trade" + liquiActiveOrders = "ActiveOrders" + liquiOrderInfo = "OrderInfo" + liquiCancelOrder = "CancelOrder" + liquiTradeHistory = "TradeHistory" + liquiWithdrawCoin = "WithdrawCoin" ) +// Liqui is the overarching type across the liqui package type Liqui struct { exchange.Base - Ticker map[string]LiquiTicker - Info LiquiInfo + Ticker map[string]Ticker + Info Info } +// SetDefaults sets current default values for liqui func (l *Liqui) SetDefaults() { l.Name = "Liqui" l.Enabled = false @@ -46,7 +48,7 @@ func (l *Liqui) SetDefaults() { l.Verbose = false l.Websocket = false l.RESTPollingDelay = 10 - l.Ticker = make(map[string]LiquiTicker) + l.Ticker = make(map[string]Ticker) l.RequestCurrencyPairFormat.Delimiter = "_" l.RequestCurrencyPairFormat.Uppercase = false l.RequestCurrencyPairFormat.Separator = "-" @@ -55,6 +57,7 @@ func (l *Liqui) SetDefaults() { l.AssetTypes = []string{ticker.Spot} } +// Setup sets exchange configuration parameters for liqui func (l *Liqui) Setup(exch config.ExchangeConfig) { if !exch.Enabled { l.SetEnabled(false) @@ -79,15 +82,18 @@ func (l *Liqui) Setup(exch config.ExchangeConfig) { } } +// GetFee returns a fee for a specific currency func (l *Liqui) GetFee(currency string) (float64, error) { + log.Println(l.Info.Pairs) val, ok := l.Info.Pairs[common.StringToLower(currency)] if !ok { - return 0, errors.New("Currency does not exist") + return 0, errors.New("currency does not exist") } return val.Fee, nil } +// GetAvailablePairs returns all available pairs func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { var pairs []string for x, y := range l.Info.Pairs { @@ -99,79 +105,77 @@ func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { return pairs } -func (l *Liqui) GetInfo() (LiquiInfo, error) { - req := fmt.Sprintf("%s/%s/%s/", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_INFO) - resp := LiquiInfo{} - err := common.SendHTTPGetRequest(req, true, l.Verbose, &resp) +// GetInfo provides all the information about currently active pairs, such as +// the maximum number of digits after the decimal point, the minimum price, the +// maximum price, the minimum transaction size, whether the pair is hidden, the +// commission for each pair. +func (l *Liqui) GetInfo() (Info, error) { + resp := Info{} + req := fmt.Sprintf("%s/%s/%s/", liquiAPIPublicURL, liquiAPIPublicVersion, liquiInfo) - if err != nil { - return resp, err - } - - return resp, nil + return resp, common.SendHTTPGetRequest(req, true, l.Verbose, &resp) } -func (l *Liqui) GetTicker(symbol string) (map[string]LiquiTicker, error) { +// GetTicker returns information about currently active pairs, such as: the +// maximum price, the minimum price, average price, trade volume, trade volume +// in currency, the last trade, Buy and Sell price. All information is provided +// over the past 24 hours. +// +// currencyPair - example "eth_btc" +func (l *Liqui) GetTicker(currencyPair string) (map[string]Ticker, error) { type Response struct { - Data map[string]LiquiTicker + Data map[string]Ticker } response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TICKER, symbol) - err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) + req := fmt.Sprintf("%s/%s/%s/%s", liquiAPIPublicURL, liquiAPIPublicVersion, liquiTicker, currencyPair) - if err != nil { - return nil, err - } - return response.Data, nil + return response.Data, + common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) } -func (l *Liqui) GetDepth(symbol string) (LiquiOrderbook, error) { +// GetDepth information about active orders on the pair. Additionally it accepts +// an optional GET-parameter limit, which indicates how many orders should be +// displayed (150 by default). Is set to less than 2000. +func (l *Liqui) GetDepth(currencyPair string) (Orderbook, error) { type Response struct { - Data map[string]LiquiOrderbook + Data map[string]Orderbook } response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_DEPTH, symbol) + req := fmt.Sprintf("%s/%s/%s/%s", liquiAPIPublicURL, liquiAPIPublicVersion, liquiDepth, currencyPair) - err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) - if err != nil { - return LiquiOrderbook{}, err - } - - depth := response.Data[symbol] - return depth, nil + return response.Data[currencyPair], + common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) } -func (l *Liqui) GetTrades(symbol string) ([]LiquiTrades, error) { +// GetTrades returns information about the last trades. Additionally it accepts +// an optional GET-parameter limit, which indicates how many orders should be +// displayed (150 by default). The maximum allowable value is 2000. +func (l *Liqui) GetTrades(currencyPair string) ([]Trades, error) { type Response struct { - Data map[string][]LiquiTrades + Data map[string][]Trades } response := Response{} - req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TRADES, symbol) + req := fmt.Sprintf("%s/%s/%s/%s", liquiAPIPublicURL, liquiAPIPublicVersion, liquiTrades, currencyPair) - err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) - if err != nil { - return []LiquiTrades{}, err - } - - trades := response.Data[symbol] - return trades, nil + return response.Data[currencyPair], + common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) } -func (l *Liqui) GetAccountInfo() (LiquiAccountInfo, error) { - var result LiquiAccountInfo - err := l.SendAuthenticatedHTTPRequest(LIQUI_ACCOUNT_INFO, url.Values{}, &result) +// GetAccountInfo returns information about the user’s current balance, API-key +// privileges, the number of open orders and Server Time. To use this method you +// need a privilege of the key info. +func (l *Liqui) GetAccountInfo() (AccountInfo, error) { + var result AccountInfo - if err != nil { - return result, err - } - - return result, nil + return result, + l.SendAuthenticatedHTTPRequest(liquiAccountInfo, url.Values{}, &result) } -//to-do: convert orderid to int64 +// Trade creates orders on the exchange. +// to-do: convert orderid to int64 func (l *Liqui) Trade(pair, orderType string, amount, price float64) (float64, error) { req := url.Values{} req.Add("pair", pair) @@ -179,51 +183,37 @@ func (l *Liqui) Trade(pair, orderType string, amount, price float64) (float64, e req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64)) - var result LiquiTrade - err := l.SendAuthenticatedHTTPRequest(LIQUI_TRADE, req, &result) + var result Trade - if err != nil { - return 0, err - } - - return result.OrderID, nil + return result.OrderID, l.SendAuthenticatedHTTPRequest(liquiTrade, req, &result) } -func (l *Liqui) GetActiveOrders(pair string) (map[string]LiquiActiveOrders, error) { +// GetActiveOrders returns the list of your active orders. +func (l *Liqui) GetActiveOrders(pair string) (map[string]ActiveOrders, error) { req := url.Values{} req.Add("pair", pair) - var result map[string]LiquiActiveOrders - err := l.SendAuthenticatedHTTPRequest(LIQUI_ACTIVE_ORDERS, req, &result) - - if err != nil { - return result, err - } - - return result, nil + var result map[string]ActiveOrders + return result, l.SendAuthenticatedHTTPRequest(liquiActiveOrders, req, &result) } -func (l *Liqui) GetOrderInfo(OrderID int64) (map[string]LiquiOrderInfo, error) { +// GetOrderInfo returns the information on particular order. +func (l *Liqui) GetOrderInfo(OrderID int64) (map[string]OrderInfo, error) { req := url.Values{} req.Add("order_id", strconv.FormatInt(OrderID, 10)) - var result map[string]LiquiOrderInfo - err := l.SendAuthenticatedHTTPRequest(LIQUI_ORDER_INFO, req, &result) - - if err != nil { - return result, err - } - - return result, nil + var result map[string]OrderInfo + return result, l.SendAuthenticatedHTTPRequest(liquiOrderInfo, req, &result) } +// CancelOrder method is used for order cancelation. func (l *Liqui) CancelOrder(OrderID int64) (bool, error) { req := url.Values{} req.Add("order_id", strconv.FormatInt(OrderID, 10)) - var result LiquiCancelOrder - err := l.SendAuthenticatedHTTPRequest(LIQUI_CANCEL_ORDER, req, &result) + var result CancelOrder + err := l.SendAuthenticatedHTTPRequest(liquiCancelOrder, req, &result) if err != nil { return false, err } @@ -231,38 +221,30 @@ func (l *Liqui) CancelOrder(OrderID int64) (bool, error) { return true, nil } -func (l *Liqui) GetTradeHistory(vals url.Values, pair string) (map[string]LiquiTradeHistory, error) { +// GetTradeHistory returns trade history +func (l *Liqui) GetTradeHistory(vals url.Values, pair string) (map[string]TradeHistory, error) { if pair != "" { vals.Add("pair", pair) } - var result map[string]LiquiTradeHistory - err := l.SendAuthenticatedHTTPRequest(LIQUI_TRADE_HISTORY, vals, &result) - - if err != nil { - return result, err - } - - return result, nil + var result map[string]TradeHistory + return result, l.SendAuthenticatedHTTPRequest(liquiTradeHistory, vals, &result) } +// WithdrawCoins is designed for cryptocurrency withdrawals. // API mentions that this isn't active now, but will be soon - you must provide the first 8 characters of the key // in your ticket to support. -func (l *Liqui) WithdrawCoins(coin string, amount float64, address string) (LiquiWithdrawCoins, error) { +func (l *Liqui) WithdrawCoins(coin string, amount float64, address string) (WithdrawCoins, error) { req := url.Values{} req.Add("coinName", coin) req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("address", address) - var result LiquiWithdrawCoins - err := l.SendAuthenticatedHTTPRequest(LIQUI_WITHDRAW_COIN, req, &result) - - if err != nil { - return result, err - } - return result, nil + var result WithdrawCoins + return result, l.SendAuthenticatedHTTPRequest(liquiWithdrawCoin, req, &result) } +// SendAuthenticatedHTTPRequest sends an authenticated http request to liqui func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { if !l.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) @@ -280,7 +262,7 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r hmac := common.GetHMAC(common.HashSHA512, []byte(encoded), []byte(l.APISecret)) if l.Verbose { - log.Printf("Sending POST request to %s calling method %s with params %s\n", LIQUI_API_PRIVATE_URL, method, encoded) + log.Printf("Sending POST request to %s calling method %s with params %s\n", liquiAPIPrivateURL, method, encoded) } headers := make(map[string]string) @@ -288,15 +270,14 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r headers["Sign"] = common.HexEncodeToString(hmac) headers["Content-Type"] = "application/x-www-form-urlencoded" - resp, err := common.SendHTTPRequest("POST", LIQUI_API_PRIVATE_URL, headers, strings.NewReader(encoded)) - + resp, err := common.SendHTTPRequest("POST", liquiAPIPrivateURL, headers, strings.NewReader(encoded)) if err != nil { return err } - response := LiquiResponse{} - err = common.JSONDecode([]byte(resp), &response) + response := Response{} + err = common.JSONDecode([]byte(resp), &response) if err != nil { return err } @@ -306,15 +287,14 @@ func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, r } jsonEncoded, err := common.JSONEncode(response.Return) - if err != nil { return err } err = common.JSONDecode(jsonEncoded, &result) - if err != nil { return err } + return nil } diff --git a/exchanges/liqui/liqui_test.go b/exchanges/liqui/liqui_test.go new file mode 100644 index 00000000..eaddeb06 --- /dev/null +++ b/exchanges/liqui/liqui_test.go @@ -0,0 +1,125 @@ +package liqui + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var l Liqui + +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + l.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + liquiConfig, err := cfg.GetExchangeConfig("Liqui") + if err != nil { + t.Error("Test Failed - liqui Setup() init error") + } + + liquiConfig.AuthenticatedAPISupport = true + liquiConfig.APIKey = apiKey + liquiConfig.APISecret = apiSecret + + l.Setup(liquiConfig) +} + +func TestGetFee(t *testing.T) { + _, err := l.GetFee("usd") + if err == nil { + t.Error("Test Failed - liqui GetFee() error", err) + } +} + +func TestGetAvailablePairs(t *testing.T) { + v := l.GetAvailablePairs(false) + if len(v) != 0 { + t.Error("Test Failed - liqui GetFee() error") + } +} + +func TestGetInfo(t *testing.T) { + _, err := l.GetInfo() + if err != nil { + t.Error("Test Failed - liqui GetInfo() error", err) + } +} + +func TestGetTicker(t *testing.T) { + _, err := l.GetTicker("eth_btc") + if err != nil { + t.Error("Test Failed - liqui GetTicker() error", err) + } +} + +func TestGetDepth(t *testing.T) { + _, err := l.GetDepth("eth_btc") + if err != nil { + t.Error("Test Failed - liqui GetDepth() error", err) + } +} + +func TestGetTrades(t *testing.T) { + _, err := l.GetTrades("eth_btc") + if err != nil { + t.Error("Test Failed - liqui GetTrades() error", err) + } +} + +func TestGetAccountInfo(t *testing.T) { + _, err := l.GetAccountInfo() + if err == nil { + t.Error("Test Failed - liqui GetAccountInfo() error", err) + } +} + +func TestTrade(t *testing.T) { + _, err := l.Trade("", "", 0, 1) + if err == nil { + t.Error("Test Failed - liqui Trade() error", err) + } +} + +func TestGetActiveOrders(t *testing.T) { + _, err := l.GetActiveOrders("eth_btc") + if err == nil { + t.Error("Test Failed - liqui GetActiveOrders() error", err) + } +} + +func TestGetOrderInfo(t *testing.T) { + _, err := l.GetOrderInfo(1337) + if err == nil { + t.Error("Test Failed - liqui GetOrderInfo() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + _, err := l.CancelOrder(1337) + if err == nil { + t.Error("Test Failed - liqui CancelOrder() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + _, err := l.GetTradeHistory(url.Values{}, "") + if err == nil { + t.Error("Test Failed - liqui GetTradeHistory() error", err) + } +} + +func TestWithdrawCoins(t *testing.T) { + _, err := l.WithdrawCoins("btc", 1337, "someaddr") + if err == nil { + t.Error("Test Failed - liqui WithdrawCoins() error", err) + } +} diff --git a/exchanges/liqui/liqui_types.go b/exchanges/liqui/liqui_types.go index bf25573d..52a8167d 100644 --- a/exchanges/liqui/liqui_types.go +++ b/exchanges/liqui/liqui_types.go @@ -1,6 +1,23 @@ package liqui -type LiquiTicker struct { +// Info holds the current pair information as well as server time +type Info struct { + ServerTime int64 `json:"server_time"` + Pairs map[string]PairData `json:"pairs"` +} + +// PairData is a sub-type for Info +type PairData struct { + DecimalPlaces int `json:"decimal_places"` + MinPrice float64 `json:"min_price"` + MaxPrice float64 `json:"max_price"` + MinAmount float64 `json:"min_amount"` + Hidden int `json:"hidden"` + Fee float64 `json:"fee"` +} + +// Ticker contains ticker information +type Ticker struct { High float64 Low float64 Avg float64 @@ -12,12 +29,14 @@ type LiquiTicker struct { Updated int64 } -type LiquiOrderbook struct { +// Orderbook references both ask and bid sides +type Orderbook struct { Asks [][]float64 `json:"asks"` Bids [][]float64 `json:"bids"` } -type LiquiTrades struct { +// Trades contains trade information +type Trades struct { Type string `json:"type"` Price float64 `json:"bid"` Amount float64 `json:"amount"` @@ -25,27 +44,8 @@ type LiquiTrades struct { Timestamp int64 `json:"timestamp"` } -type LiquiResponse struct { - Return interface{} `json:"return"` - Success int `json:"success"` - Error string `json:"error"` -} - -type LiquiPair struct { - DecimalPlaces int `json:"decimal_places"` - MinPrice float64 `json:"min_price"` - MaxPrice float64 `json:"max_price"` - MinAmount float64 `json:"min_amount"` - Hidden int `json:"hidden"` - Fee float64 `json:"fee"` -} - -type LiquiInfo struct { - ServerTime int64 `json:"server_time"` - Pairs map[string]LiquiPair `json:"pairs"` -} - -type LiquiAccountInfo struct { +// AccountInfo contains full account details information +type AccountInfo struct { Funds map[string]float64 `json:"funds"` Rights struct { Info bool `json:"info"` @@ -57,14 +57,8 @@ type LiquiAccountInfo struct { OpenOrders int `json:"open_orders"` } -type LiquiTrade struct { - Received float64 `json:"received"` - Remains float64 `json:"remains"` - OrderID float64 `json:"order_id"` - Funds map[string]float64 `json:"funds"` -} - -type LiquiActiveOrders struct { +// ActiveOrders holds active order information +type ActiveOrders struct { Pair string `json:"pair"` Type string `json:"sell"` Amount float64 `json:"amount"` @@ -73,7 +67,8 @@ type LiquiActiveOrders struct { Status int `json:"status"` } -type LiquiOrderInfo struct { +// OrderInfo holds specific order information +type OrderInfo struct { Pair string `json:"pair"` Type string `json:"sell"` StartAmount float64 `json:"start_amount"` @@ -83,12 +78,22 @@ type LiquiOrderInfo struct { Status int `json:"status"` } -type LiquiCancelOrder struct { +// CancelOrder holds cancelled order information +type CancelOrder struct { OrderID float64 `json:"order_id"` Funds map[string]float64 `json:"funds"` } -type LiquiTradeHistory struct { +// Trade holds trading information +type Trade struct { + Received float64 `json:"received"` + Remains float64 `json:"remains"` + OrderID float64 `json:"order_id"` + Funds map[string]float64 `json:"funds"` +} + +// TradeHistory contains trade history data +type TradeHistory struct { Pair string `json:"pair"` Type string `json:"type"` Amount float64 `json:"amount"` @@ -98,7 +103,15 @@ type LiquiTradeHistory struct { Timestamp float64 `json:"timestamp"` } -type LiquiWithdrawCoins struct { +// Response is a generalized return type +type Response struct { + Return interface{} `json:"return"` + Success int `json:"success"` + Error string `json:"error"` +} + +// WithdrawCoins shows the amount of coins withdrawn from liqui not yet available +type WithdrawCoins struct { TID int64 `json:"tId"` AmountSent float64 `json:"amountSent"` Funds map[string]float64 `json:"funds"` From a86462b338da3d686185b8e4116f8401f6ff208e Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Mon, 18 Sep 2017 15:58:24 +1000 Subject: [PATCH 32/32] Fixed merge issues, fixed race condition in gemini package. --- common/common.go | 6 ----- exchanges/gemini/gemini.go | 6 +++++ exchanges/wex/wex.go | 8 +++---- portfolio/portfolio.go | 47 +------------------------------------- 4 files changed, 11 insertions(+), 56 deletions(-) diff --git a/common/common.go b/common/common.go index 1d4da4ce..2717e727 100644 --- a/common/common.go +++ b/common/common.go @@ -306,13 +306,7 @@ func SendHTTPGetRequest(url string, jsonDecode, isVerbose bool, result interface } if res.StatusCode != 200 { -<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 - log.Printf("HTTP status code: %d\n", res.StatusCode) - log.Printf("URL: %s\n", url) - return errors.New("status code was not 200") -======= return fmt.Errorf("common.SendHTTPGetRequest() error: HTTP status code %d", res.StatusCode) ->>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. } contents, err := ioutil.ReadAll(res.Body) diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index e83585f5..9ff8c6b9 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -7,6 +7,7 @@ import ( "net/url" "strconv" "strings" + "sync" "time" "github.com/thrasher-/gocryptotrader/common" @@ -71,10 +72,13 @@ var ( // needed append the sandbox function as well. type Gemini struct { exchange.Base + M sync.Mutex } // AddSession adds a new session to the gemini base func (g *Gemini) AddSession(sessionID int, apiKey, apiSecret, role string, needsHeartbeat bool) error { + g.M.Lock() + defer g.M.Unlock() if sessionAPIKey == nil { IsSession = true sessionAPIKey = make(map[int]string) @@ -139,6 +143,8 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { // Session is a session manager for differing APIKeys and roles, use this for all function // calls in this package func (g *Gemini) Session(sessionID int) *Gemini { + g.M.Lock() + defer g.M.Unlock() g.APIUrl = geminiAPIURL _, ok := sessionAPIKey[sessionID] if !ok { diff --git a/exchanges/wex/wex.go b/exchanges/wex/wex.go index 3ac246c1..ee5ba2fe 100644 --- a/exchanges/wex/wex.go +++ b/exchanges/wex/wex.go @@ -94,7 +94,7 @@ func (w *WEX) GetFee() float64 { func (w *WEX) GetInfo() (Info, error) { req := fmt.Sprintf("%s/%s/%s/", wexAPIPublicURL, wexAPIPublicVersion, wexInfo) resp := Info{} - err := common.SendHTTPGetRequest(req, true, &resp) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &resp) if err != nil { return resp, err @@ -111,7 +111,7 @@ func (w *WEX) GetTicker(symbol string) (map[string]Ticker, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTicker, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data) if err != nil { return nil, err @@ -128,7 +128,7 @@ func (w *WEX) GetDepth(symbol string) (Orderbook, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexDepth, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data) if err != nil { return Orderbook{}, err } @@ -146,7 +146,7 @@ func (w *WEX) GetTrades(symbol string) ([]Trades, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTrades, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, w.Verbose, &response.Data) if err != nil { return nil, err } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index 9a8d7632..ff486a79 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -90,62 +90,17 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } -<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 // GetCryptoIDAddress queries CryptoID for an address balance for a // specified cryptocurrency func GetCryptoIDAddress(address string, coinType string) (float64, error) { ok, err := common.IsValidCryptoAddress(address, coinType) if !ok || err != nil { return 0, errors.New("invalid address") -======= -// GetBlockrBalanceSingle queries Blockr for an address balance for either a -// LTC or a BTC single address -func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { - valid, _ := common.IsValidCryptoAddress(address, coinType) - if !valid { - return BlockrAddressBalanceSingle{}, fmt.Errorf( - "Not a %s address", common.StringToUpper(coinType), - ) } - url := fmt.Sprintf( - "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, - blockrAPIVersion, blockrAddressBalance, address, - ) - result := BlockrAddressBalanceSingle{} - err := common.SendHTTPGetRequest(url, true, false, &result) - if err != nil { - return result, err - } - if result.Status != "success" { - return result, errors.New(result.Message) ->>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. - } - -<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 var result interface{} url := fmt.Sprintf("%s/%s/api.dws?q=getbalance&a=%s", cryptoIDAPIURL, common.StringToLower(coinType), address) - err = common.SendHTTPGetRequest(url, true, &result) -======= -// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC -// or a BTC multiple addresses -func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { - for _, add := range addresses { - valid, _ := common.IsValidCryptoAddress(add, coinType) - if !valid { - return BlockrAddressBalanceMulti{}, fmt.Errorf( - "Not a %s address", common.StringToUpper(coinType), - ) - } - } - addressesStr := common.JoinStrings(addresses, ",") - url := fmt.Sprintf( - "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, - blockrAPIVersion, blockrAddressBalance, addressesStr, - ) - result := BlockrAddressBalanceMulti{} - err := common.SendHTTPGetRequest(url, true, false, &result) ->>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. + err = common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return 0, err }